1 / 24

第 9 章 多线程

第 9 章 多线程. 王德俊 上海交通大学继续教育学院. 第 9 章 多线程. 9.1 线程及其实现方法 9.2 线程的同步控制. 9.2 线程的同步控制. 9.2.1 为什么要同步控制 9.2.2 使用 ManualResetEvent 类 9.2.3 使用 AutoResetEvent 类. 9.2.1 为什么要同步控制. 线程之间由于共享资源而产生相互之间需要互相等待、互通消息 才能正确地完成任务,此时就 需要相应的同步控制机制 来支持线程的合作关系。. 9.2 线程的同步控制. 9.2.1 为什么要同步控制.

amato
Download Presentation

第 9 章 多线程

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第9章 多线程 王德俊 上海交通大学继续教育学院

  2. 第9章多线程 9.1 线程及其实现方法 9.2 线程的同步控制

  3. 9.2 线程的同步控制 9.2.1 为什么要同步控制 9.2.2 使用ManualResetEvent类 9.2.3 使用AutoResetEvent类

  4. 9.2.1 为什么要同步控制 • 线程之间由于共享资源而产生相互之间需要互相等待、互通消息才能正确地完成任务,此时就需要相应的同步控制机制来支持线程的合作关系。

  5. 9.2 线程的同步控制 9.2.1 为什么要同步控制 【例9.1】存在同步访问问题的多线程程序。如下:创建控制台应用程序BankTransfering,简单地模拟银行用户进行转帐和取款的程序: class Bank { private double account1 = 2500; private double account2 = 1000; public void transfering() //转帐 { Console.WriteLine("转帐前帐户account1还剩余的金额:" + account1.ToString()); Console.Write("转帐金额(元):"); double sum = double.Parse(Console.ReadLine()); //输入转帐金额 if (sum > account1) { Console.WriteLine("转帐金额超出了帐户account1所剩的金额,"+"转帐失败!"); return; } account1 = account1 - sum; account2 = account2 + sum; Console.WriteLine("转帐后帐户account1还剩余的金额:" + account1.ToString()); }

  6. 9.2 线程的同步控制 9.2.1 为什么要同步控制 public void fetching() //取款 { Thread.Sleep(100); account1 = account1 - 2000; //取款2000元 } } static void Main(string[] args) { Bank a = new Bank(); Thread user1 = new Thread(new ThreadStart(a.transfering)); Thread user2 = new Thread(new ThreadStart(a.fetching)); user1.Start(); user2.Start(); Console.ReadKey(); }

  7. 9.2 线程的同步控制 9.2.1 为什么要同步控制 • 程序运行结果如图: • 分析:user1查询帐户account1时,显示了还剩2500元的信息,但在执行从account1向account2转2000元时,却出现了操作失败的提示。 • 其原因:恰好在user1等待接收从键盘输入的转帐金额时,user2从帐户account1上提走了2000元。 • 结论:需要同步控制

  8. 9.2 线程的同步控制 9.2.2 使用ManualResetEvent类 • ManualResetEvent类的作用是:通知一个或多个正在等待的线程已发生事件。 • ManualResetEvent类对象有两种状态:有信号状态和无信号状态。状态常通过两种方法设置: • 一种是使用构造函数, 例如: ManualResetEvent mre = newManualResetEvent( false); //初始化mre为无信号状态 ManualResetEvent mre = newManualResetEvent(true); //初始化mre为有信号状态 • 另一种是使用对象方法, 例如: mre.Reset(); //使mre处于无信号状态 mre.Set(); //使mre处于有信号状态 调用 Set 方法将使等待句柄一直保持终止状态,允许一个或多个等待线程继续,直到 Reset方法被调用。

  9. 9.2 线程的同步控制 9.2.2 使用ManualResetEvent类 • 当ManualResetEvent类对象处于无信号状态时,调用该对象WaitOne()方法的线程将被阻止运行(暂停); • 当该对象变为处于有信号状态时, WaitOne()方法收到信号, WaitOne()方法将解除该线程的暂停状态,使它继续运行。 • 实现多线程的同步控制方法是: • 将被视为一体的语句序列置于Reset()和Set()方法之间(称为“加锁”) • 需要与它们同步的线程,在读取共享变量前先调用WaitOne()方法;用于检测ManualResetEvent类对象有无信号: • 无信号,则该线程被暂停 • 有信号,该线程才能继续执行,从而实现线程的同步控制。

  10. 9.2 线程的同步控制 9.2.2 使用ManualResetEvent类 1. 单线程的加锁 2. 多线程的加锁

  11. 1. 单线程的加锁 • 对程序BankTransfering出现问题的解决办法: • 通过创建ManualResetEvent类的对象mre,并适当地添加mre.Reset()和mre.Set() 添加同步控制 • 代码修改如下(红色部分): • class Bank • { • private double account1 = 2500; • private double account2 = 1000; • //创建ManualResetEvent类的对象mre • public ManualResetEvent mre = new ManualResetEvent(false);

  12. public void transfering() //转帐 { mre.Reset(); //设置对象mre处于无信号状态 Console.WriteLine("转帐 前 帐户account1还剩余的金额:" + account1.ToString()); Console.Write("转帐金额(元):"); double sum = double.Parse(Console.ReadLine()); if (sum > account1) { Console.WriteLine("转帐金额超出了帐户account1所剩的金额," +"转帐失败!"); return; }

  13. account1 = account1 - sum; account2 = account2 + sum; Console.WriteLine("转帐 后 帐户account1还剩余的金额:" +account1.ToString()); mre.Set(); //设置对象mre处于有信号状态 } public void fetching() //取款 { //阻止当前线程(线程user2)的运行,直到收到对象mre发的信息 mre.WaitOne(); Thread.Sleep(100); account1 = account1 - 2000; } }

  14. 2. 多线程的加锁 • 问题: • 一个ManualResetEvent类对象只能对一个线程中的语句序列进行加锁; • 如果需要对多个线程中的语句序列进行加锁,就需要创建与线程数量一样多的ManualResetEvent类对象。

  15. 2. 多线程的加锁 【例9.2】下列是控制台应用程序BankTransfering2中文件Program.cs的代码,它仍然模拟银行转帐、查账的功能,但对代码进行了简化(该程序需要解决多线程的同步控制问题): class Bank { private double account1 = 2500; private double account2 = 1000; public void transfering() //将100元从帐户account1转到帐户account2 { account1 = account1 - 100; Thread.Sleep(100); account2 = account2 + 100; }

  16. 2. 多线程的加锁 public void transfering2() //将300元从帐户account2转到帐户account1 { account1 = account1 + 300; Thread.Sleep(200); account2 = account2 - 300; } public void querying() //查询帐户account1和account2上的余额 { Console.WriteLine("帐户account1上的余额为:{0} 元", account1); Console.WriteLine("帐户account2上的余额为:{0} 元", account2); } }

  17. 2. 多线程的加锁 static void Main(string[] args) { Bank a = new Bank(); Thread user1 = new Thread(newThreadStart(a.transfering)); //转帐用户1 Thread user2 = new Thread(newThreadStart(a.transfering2)); //转帐用户2 Thread user3 = new Thread(newThreadStart(a.querying)); //查账用户 user1.Start(); //执行转帐(account1到account2) user2.Start(); //执行转帐(account2到account1) user3.Start(); //查账用户 Console.ReadKey(); }

  18. 2. 多线程的加锁 • 实现对两个线程中的语句进行加锁: • (1)先创建一个包含两个ManualResetEvent类对象的ManualResetEvent数组mres:ManualResetEvent[] mres = { new ManualResetEvent(false), • new ManualResetEvent(false) }; • (2)用数组mres中的两个对象分别对方法transfering()和方法transfering2()中的代码进行加锁; • (3)在方法querying()中查询语句之前调用WaitHandle.WaitAll()方法,该方法的参数类型是ManualResetEvent数组,其作用是:当数组中所有的对象都接收到信号后才允许方法querying()继续执行。

  19. 2. 多线程的加锁 修改后的代码: class Bank { private double account1 = 2500; private double account2 = 1000; ManualResetEvent[] mres = { new ManualResetEvent(false), new ManualResetEvent(false) }; //创建包含两个ManualResetEvent类对象的数组 public void transfering() //将100元从帐户account1转到帐户account2 { mres[0].Reset(); account1 = account1 - 100; Thread.Sleep(100); account2 = account2 + 100; mres[0].Set(); }

  20. 2. 多线程的加锁 public void transfering2() //将300元从帐户account2转到帐户account1 { mres[1].Reset(); account1 = account1 + 300; Thread.Sleep(200); account2 = account2 - 300; mres[1].Set(); } public void querying() //查询帐户account1和account2上的余额 { WaitHandle.WaitAll(mres); Console.WriteLine("帐户account1上的余额为:{0} 元", account1); Console.WriteLine("帐户account2上的余额为:{0} 元", account2); } }

  21. 9.2 线程的同步控制 9.2.3 使用AutoResetEvent类 • 分析: • ManualResetEvent的缺点:当进行多线程的同步控制时,创建的ManualResetEvent类对象的数量要与线程的个数相同,这使程序代码显得比较累赘。 • AutoResetEvent的特点:只需要创建一个AutoResetEvent对象,就可以完成对多个线程的同步控制。 • AutoResetEvent的Set()方法发出“一条”信号,就 “消掉”一个WaitOne()方法; • 如果还有其他WaitOne()方法在等待信号,那么AutoResetEvent对象会自动变为无信号状态(如果没有就不改变其状态),直到再次执行一个Set()方法才能“消掉”下一个WaitOne()方法。

  22. 9.2 线程的同步控制 9.2.3 使用AutoResetEvent类 【例9.3】修改程序BankTransfering2(见例9.2),使用AutoResetEvent类实现对其所涉及线程的同步控制。 class Bank { private double account1 = 2500; private double account2 = 1000; AutoResetEvent are = newAutoResetEvent(false); public void transfering() //将100元从帐户account1转到帐户account2 { are.Reset(); account1 = account1 - 100; Thread.Sleep(100); account2 = account2 + 100; are.Set(); }

  23. 9.2 线程的同步控制 9.2.3 使用AutoResetEvent类 public void transfering2() //将300元从帐户account2转到帐户account1 { are.Reset(); account1 = account1 + 300; Thread.Sleep(200); account2 = account2 - 300; are.Set(); } public void querying() //查询帐户account1和account2上的余额 { are.WaitOne(); are.WaitOne(); Console.WriteLine("帐户account1上的余额为:{0} 元", account1); Console.WriteLine("帐户account2上的余额为:{0} 元", account2); } } 注意:有多少个Set()方法就应该多少个WaitOne()方法与之对应,否则会出现无限等待或其他问题。

  24. 本讲小结 为什么要同步控制 使用ManualResetEvent类 使用AutoResetEvent类

More Related