在上一篇文章“.NET简谈组件程序设计之(上下文与同步域) ”中,我们学习了关于一些上下文和同步域的概念,可以利用这两个技术来进行自动同步。

今天我们主要学习怎么手动来执行同步,能从更小的粒度进行封锁,以达到最大程度的吞吐量。[王清培版权所有,转载请给出署名]

我们知道线程是进程的运行实体,进程是资源分配单位,而线程是执行单位。照书上所说,线程是程序的执行路径,当我们分配一个线程的时候,要确定线程的执行路径是什么,也就是代码中的ThreadStart委托所指向的入口点方法。

一旦我们手动Start启动线程的时候,当前上下文线程就被系统立即切换,我们从Thread.CurrentThread静态属性中可以准确的获取到当前正在执行的线程实例,然后进行一系列的设置、等待、终止。[王清培版权所有,转载请给出署名]

那么线程到底是怎么实现同步(互斥)的呢,在我们正常的代码中是没有关于线程同步的现实代码的,所以在线程执行路径中是不存在任何能够阻塞线程的实现代码。要想实现线程阻塞,就必须在线程的执行路径中写点东西,让所有线程当进入这段代码的时候(也就是临界资源),通过判断某种东西来确定是否允许进入执行。

图1:

在ThreadStartEnterPoint方法中,如果没有任何的同步代码,那么任何线程都能进去执行,就导致了乱七八糟的数据。当数据在内存中的时候,在同一时间只能是由CPU去执行线程的代码,但是线程是有竞争情况的,当线程1还没有完全执行完毕,线程2就来执行这块数据,导致数据的不同步。

那么我们需要再线程1还没有执行完毕前不允许其他线程使用这块内存对象。当线程1使用完后就立即释放所占有的资源,让其他线程能竞争。

利用Monitor(监视器)来进行同步

Monitor是用来提供同步的对象,通过它可以在某个时间点上锁定对象。请看代码:

  1. [MethodImpl(MethodImplOptions.Synchronized)]
  2. public void ShowMessage()
  3. {
  4. for (int i = 0; i < 20; i++)
  5. {
  6. if (i == 10)
  7. Monitor.Wait(this);//等第二个线程使用完后,我在继续执行。将当前线程放置在等待队列里
  8. Thread currentthread = Thread.CurrentThread;
  9. Console.WriteLine(currentthread.Name + "|" + currentthread.ManagedThreadId + "|" + i.ToString());
  10. }
  11. }
  12. [MethodImpl(MethodImplOptions.Synchronized)]
  13. public void PluseThread()
  14. {
  15. for (int i = 0; i < 20; i++)
  16. {
  17. Thread currentthread = Thread.CurrentThread;
  18. Console.WriteLine(currentthread.Name + "|" + currentthread.ManagedThreadId + "|" + i.ToString());
  19. }
  20. Monitor.Pulse(this);//将等待队列里的线程放到锁定队列,也就是Monitor.Enter();
  21. }

这是一个类中的两个方法,在方法的头部我用了MethodImpl方法特性进行了标识,其实这个特性的目的就是在方法的入口处和结束处加上同步方法,也就是Monitor.Enter和Monitor.Exit,一般情况下我们都是习惯用lock来锁定对象,其实lock也是Monitor的变体。这里我就不写出来了。让我们熟悉一下陌生的使用方式。

  1. Myclass1 myclass = new Myclass1();
  2. ThreadStart startdeleted = new ThreadStart(myclass.ShowMessage);
  3. Thread thread = new Thread(startdeleted);
  4. thread.Name = "线程1";
  5. ThreadStart stra = new ThreadStart(myclass.PluseThread);
  6. Thread thread2 = new Thread(stra);
  7. thread2.Name = "线程2";
  8. thread.Start();
  9. thread2.Start();
  10. thread2.Join();
  11. Console.WriteLine("线程2已经释放");
  12. thread.Join();
  13. Console.WriteLine("线程1已经释放");

在调用的代码里面,大概意思是这样的,我们同时开启两个线程,入口点分别是上面的两个方法,在PluseThread里面是为了将ShowMessage线程1从等待队列里释放出来继续执行。[王清培版权所有,转载请给出署名]

在ShowMessage里面我用Monitor.Wait方法等待,当调用这个方法的时候会使用我锁定的对象,让其他线程进入执行。当Monitor.Pluse的时候,线程1继续执行。

静态Monitor对象是每个线程都会执行的路径,我们通过控制Monitor来进行线程同步,当我们调用Wait就是等待,直到当前对象Pluse才继续执行。

图2:

利用WaitHandle(等待句柄)来进行同步

上面我们通过Monitor来进行同步,在同步的时候我们需要很好的控制等待时间,用Monitor也能通过Wait进行等待超时设置,也许它内部封装是Windows等待句柄。

这里我们通过使用WaitHandle来进行同步,WaitHandle是个抽象类,它的子类有很多,比如Mutex互斥体、ManualResetEvent、AutoResetEvent事件对象,等等。下面我们就来看看利用这些对象怎么同步线程。

Mutext互斥体

  1. public void Print()
  2. {
  3. Mutex mutex = new Mutex(false, "myclass");//Mutex互斥体
  4. mutex.WaitOne();
  5. for (int i = 0; i < 10; i++)
  6. {
  7. Console.WriteLine(i.ToString() + Thread.CurrentThread.Name);
  8. }
  9. mutex.ReleaseMutex();
  10. }

在方法的内部我们申请一个Mutex对象,这个Mutex是全局的,就是在一台机器上只能存在一个名称的Mutex,Mutex可用来同步线程也可以用来同步进程。

我们在Print方法里面用WaitOne获取句柄,如果已经有线程“捷足先得”了,那么这里将阻塞,并返回false。

在使用完后,记得调用ReleaseMutex释放当前占用的Mutex句柄。

  1. Myclass1 myclass = new Myclass1();
  2. Thread thread = new Thread(new ThreadStart(myclass.Print));
  3. thread.Name = "线程1";
  4. Thread thread2 = new Thread(new ThreadStart(myclass.Print));
  5. thread2.Name = "线程2";
  6. thread.Start();
  7. thread2.Start();
  8. thread2.Join();
  9. thread.Join();
  10. Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
  11. Console.ReadLine();

图3:

利用ManualResetEvent(手动事件)来进行同步

我们直接看代码吧,ManualResetEvent大家可能都用过。

  1. public class EventWaitHandlerDemo
  2. {
  3. ManualResetEvent resetevent = new ManualResetEvent(false);
  4. public EventWaitHandlerDemo()
  5. {
  6. Thread thread = new Thread(new ThreadStart(this.DoWork));
  7. thread.Start();
  8. }
  9. private void DoWork()
  10. {
  11. int count = 0;
  12. while (true)
  13. {
  14. resetevent.WaitOne();
  15. Console.WriteLine(count++);
  16. }
  17. }
  18. public void GoThread()
  19. {
  20. resetevent.Set();
  21. Console.WriteLine("线程启动");
  22. }
  23. public void Stopthread()
  24. {
  25. resetevent.Reset();
  26. Console.WriteLine("线程暂停");
  27. }
  28. public void Close()
  29. {
  30. resetevent.Close();
  31. Console.WriteLine("线程终止");
  32. }
  33. }

这种类型的等待句柄对象是完全手动控制的,让我们想要用的时候要记得set,想要暂停的时候就Reset,不用了就close。

  1. EventWaitHandlerDemo demo = new EventWaitHandlerDemo();
  2. demo.GoThread();
  3. Thread.Sleep(1000);
  4. demo.Stopthread();
  5. Thread.Sleep(1000);
  6. demo.Close();
  7. Console.WriteLine("程序结束");

图4:

利用AutoResetEvent(自动事件)来进行同步

从名字上就能看出,该事件是自动重置事件,不需要想上面那样进行set\reset操作。

  1. public class AutoResetEventDemo
  2. {
  3. AutoResetEvent autoevent = new AutoResetEvent(true);
  4. public AutoResetEventDemo()
  5. { }
  6. public void Print()
  7. {
  8. autoevent.WaitOne();
  9. //  autoevent.Reset();在手动事件中,需要手动切换状态
  10. int i = 0;
  11. while (true)
  12. {
  13. if (i == 10)
  14. {
  15. Console.WriteLine(Thread.CurrentThread.Name + "线程结束");
  16. autoevent.Set();
  17. break;
  18. }
  19. Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "|" + i++);
  20. }
  21. }
  22. }

在上面的代码中,我们通过WaitOne获取等待句柄,当我们获取到之后,事件对象会自动重置为信号已发,其他线程无法获取到等待句柄。当我们set之后其他线程才能获取到,这里省掉的是线程进入执行路径的过程。

ManualResetEvent需要手动进行set才能使用,一旦set之后信号标记为未发状态,所有线程都能执行代码,除非手动Reset才能阻塞。

  1. Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
  2. AutoResetEventDemo autodemo = new AutoResetEventDemo();
  3. Thread thread1 = new Thread(new ThreadStart(autodemo.Print));
  4. thread1.Name = "线程1";
  5. Thread thread2 = new Thread(new ThreadStart(autodemo.Print));
  6. thread2.Name = "线程2";
  7. thread1.Start();
  8. thread2.Start();
  9. thread1.Join();
  10. thread2.Join();
  11. Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
  12. Console.Read();

图5:

[王清培版权所有,转载请给出署名]

.NET简谈组件程序设计之(手动同步)相关推荐

  1. .NET简谈组件程序设计之(上下文与同步域)

    我们继续学习.NET多线程技术,这篇文章的内容可能有点复杂.在打破常理之后,换一种新的思考模型最为头疼.这篇文章里面会涉及到一些不太常见的概念,比如:上下文.同步域等等.我也是最近才接触这些关于组件编 ...

  2. .NET简谈组件程序设计之(渗入序列化过程)

    在本人的上一篇文章".NET简谈组件程序设计之(初识序列化.持久化) "中,我们基本上了解了什么叫序列化和持久化.通过系统为我们提供的服务,我们可以很方便的进行二进制序列化.SOA ...

  3. .NET简谈组件程序设计之(异步委托)

    说到委托我想大家基本上都用过的,今天这篇文章就来讲解关于委托的异步奥秘. 在我们正常使用的时候很少会去用异步委托技术来提高代码效率.委托的好处就是能对方法进行面向对象的封装,随意传递.在任何组件客户代 ...

  4. .NET简谈组件程序设计之(详解NetRemoting结构)

    在本人的上一篇文章中只是简单的介绍了一下.NETRemoting的一般概念和基本的使用.这篇文章我想通过自己的学习和理解将对.NETRemoting的整体的一个框架进行通俗的讲解,其中最重要的就是信道 ...

  5. .NET简谈组件程序设计之(AppDomain应用程序域)

    最近在苦学.NET底层框架模型,发现.NET深入真的不是一般的难,不开源.没有相关系统的官方的书籍做学习资料,只能零散的看MSDN.要想摸熟.NET的模型真的并非易事.慢慢来吧.[王清培版权所有,转载 ...

  6. 异步复位同步释放_简谈同步复位和异步复位

    简谈同步复位和异步复位 大侠们,江湖偌大,有缘相见,欢迎一叙,今天来聊一聊数字电路设计中的同步复位和异步复位. 谈到同步复位和异步复位,那咱们就不得不来聊一聊复位这个词了.在数字逻辑电路设计中,电路通 ...

  7. .Net组件程序设计之线程、并发管理(二)

    .Net组件程序设计之线程.并发管理(二) 2.同步线程 手动同步 监视器 互斥 可等待事件 同步线程 所有的.NET组件都支持在多线程的环境中运行,可以被多个线程并发访问,如果没有线程同步,这样的后 ...

  8. .NET简谈事务、分布式事务处理

    在本人的 " .NET简谈事务本质论"一文中我们从整体上了解了事务模型,在我们脑子里能有一个全局的事务处理结构,消除对数据库事务的依赖理解,重新认识事务编程模型. 今天这篇文章我们 ...

  9. 简谈FPGA的上电复位

    简谈FPGA的上电复位 今天和大侠简单聊一聊FPGA设计中的上电复位,话不多说,上货. 在基于verilog的FPGA设计中,我们常常可以看到以下形式的进程: 信号rst_n用来对进程中所用变量的初始 ...

最新文章

  1. 刚学会深拷贝一个对象,学妹却问我怎么深拷贝一个图
  2. Android生命周期函数执行顺序
  3. MySQL Cluster 群集安装环境介绍
  4. 1.7 编程基础之字符串 14 大小写字母互换 python
  5. static代码块什么时候运行_健康的代码:什么时候该注释?
  6. 51nod1134最长递增子序列(dp)
  7. 好用的shell_Linux系统安全 | Linux中的Shell和Bash
  8. Day10 sambaNFS(Enginner04)
  9. android定位问题
  10. 中文文本纠错工具推荐:pycorrector
  11. android drm框架分析,如何使用android.drm框架
  12. 微信开发工具 git代码管理
  13. 立创开源 | 恒温加热台
  14. Linux进阶 | 超详细全方面的Docker Swarm Web集群介绍与部署!
  15. 智合同丨企业数智化转型,AI技术起到了什么作用?
  16. windows文件服务器高可用,通过 Windows Server 2012 构建高可用性的文件服务器
  17. 触摸!天空龙 - 锻炼极速反应力
  18. WAF检测率及误报测试工具Gotestwaf
  19. k-9 邮箱添加 qq、163、gmail 帐号
  20. Android 第三方QQ分享功能实现

热门文章

  1. 【杂谈】超过12个,150页深度学习开源框架指导手册与GitHub项目,初学CV你值得拥有...
  2. 全球及中国聚酰胺市场总体规模与供需现状分析报告2022年
  3. 全球及中国水产加工市场消费潜力分析与投资规模建议报告2022版
  4. WINDOWS 逻辑坐标 设备坐标 屏幕坐标 客户区坐标
  5. php h5用户信息,【php】PHP怎样防止用户注册高仿其他人的用户名?
  6. PHP下实现两种ajax跨域的解决方案之jsonp
  7. Vue.js的复用组件开发流程
  8. HRBUST 2011【简单dp】
  9. iOS开发之功能模块--推送之坑问题解决
  10. C#对事务的代码封装