首页 > .NET简谈组件程序设计之(手动同步)

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

在上一篇文章“.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.  
  3.            ThreadStart startdeleted = new ThreadStart(myclass.ShowMessage);  
  4.            Thread thread = new Thread(startdeleted);  
  5.            thread.Name = "线程1";  
  6.            ThreadStart stra = new ThreadStart(myclass.PluseThread);  
  7.            Thread thread2 = new Thread(stra);  
  8.            thread2.Name = "线程2";  
  9.  
  10.            thread.Start();  
  11.            thread2.Start();  
  12.  
  13.            thread2.Join();  
  14.            Console.WriteLine("线程2已经释放");  
  15.            thread.Join();  
  16.            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.  
  3.            Thread thread = new Thread(new ThreadStart(myclass.Print));  
  4.            thread.Name = "线程1";  
  5.  
  6.            Thread thread2 = new Thread(new ThreadStart(myclass.Print));  
  7.            thread2.Name = "线程2";  
  8.  
  9.            thread.Start();  
  10.            thread2.Start();  
  11.  
  12.            thread2.Join();  
  13.            thread.Join();  
  14.            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);  
  15.            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.  
  4.             Thread.Sleep(1000);  
  5.             demo.Stopthread();  
  6.  
  7.             Thread.Sleep(1000);  
  8.             demo.Close();  
  9.  
  10.             Console.WriteLine("程序结束"); 

图4:

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

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

  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.  
  4.            Thread thread1 = new Thread(new ThreadStart(autodemo.Print));  
  5.            thread1.Name = "线程1";  
  6.  
  7.            Thread thread2 = new Thread(new ThreadStart(autodemo.Print));  
  8.            thread2.Name = "线程2";  
  9.            thread1.Start();  
  10.            thread2.Start();  
  11.  
  12.            thread1.Join();  
  13.            thread2.Join();  
  14.            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);  
  15.            Console.Read(); 

图5:

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

更多相关:

  • 多线程有什么好处?提高CPU的利用率,更好地利用系统资源,使用Monitor类可以同步静态/实例化的方法的全部代码或者部分代码段,使用不同的同步类创建自己的同步机制。多线程指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程提升整体处理性能。多线程是指程序中包含多个执行流,即...

  • Step1:在界面主函数的构造函数中初始化多线程 auto mythread = new QThread(); //新建connect(mythread , &QThread::finished, mythread, &QObject::deleteLater);//线程运行结束后释放内存object1->moveToThread...

  • 一、thread的基本用法 参见C++使用thread类多线程编程 。 二、类外使用多线程,访问类的成员 这几种方式,新建线程都是在类外,然后通过把友元函数或者成员函数作为thread参数。 #include #include #include using namesp...

  • 本博文是根据中科大信息学院谭立湘老师的课件加上自己的理解整理出来的 ************************************************************************************ NVIDIA在2007年推出CUDA这个统一计算架构 CUDA的基本思想是支持大量的线程级并...

  • 一、parallel communication patterns   并行通信模式 Map:映射,在特定的位置读取和写入。 Gather:收集,从多个不同的位置读入,写入一个位置。 Scatter:分发,写入多个位置。 Transpose转置 结构数组缩写为AOS,数组结构缩写为SOA 转置运算是指任务重新排序内存中的数...

  • 英语的重要性,毋庸置疑!尤其对广大职场人士,掌握英语意味着就多了一项竞争的技能。那,对于我们成人来说,时间是最宝贵的。如何短时间内在英语方面有所突破,这是我们最关心的事情。英语学习,到底有没有捷径可以走,是否可以速成?周老师在这里明确告诉大家,英语学习,没有绝对的捷径走,但是可以少走弯路。十多年的教学经验告诉我们,成功的学习方法可以借...

  • 展开全部 其实IDLE提供了一个显32313133353236313431303231363533e78988e69d8331333365663438示所有行和所有字符的功能。 我们打开IDLE shell或者IDLE编辑器,可以看到左下角有个Ln和Col,事实上,Ln是当前光标所在行,Col是当前光标所在列。 我们如果想得到文件代码...

  • 前言[1]从 Main 方法说起[2]走进 Tomcat 内部[3]总结[4]《Java 2019 超神之路》《Dubbo 实现原理与源码解析 —— 精品合集》《Spring 实现原理与源码解析 —— 精品合集》《MyBatis 实现原理与源码解析 —— 精品合集》《Spring MVC 实现原理与源码解析 —— 精品合集》《Spri...

  • 【本文摘要】【注】本文所述内容为学习Yjango《学习观》相关视频之后的总结,观点归Yjango所有,本文仅作为学习之用。阅读本节,会让你对英语这类运动类知识的学习豁然开朗,你会知道英语学习方面,我们的症结所在。学习英语这类运动类知识,需要把握四个原则第一,不要用主动意识。第二,关注于端对端第三,输入输出符合实际情况第四,通过多个例子...

  • 点云PCL免费知识星球,点云论文速读。文章:RGB-D SLAM with Structural Regularities作者:Yanyan Li , Raza Yunus , Nikolas Brasch , Nassir Navab and Federico Tombari编译:点云PCL代码:https://github.co...

  • 以前经常在github上下载一些开源项目,比如crtmpserver, nginx, apache traffic server, ffmpeg等, 一直不知道怎么同步更新到当前的最新源码. 我以前的做法,另找一个目录,重新下载这些开源项目.今天终于学到一招, 顺利同步更新这些开源代码. 方法其实很简单, 直接进入某个开源项目下面,...

  • 1、本地仓库与远程仓库第一次同步时,一直同步不上 最后 git status ,发现有两个文件没提交 提交后再push即可  2、如果不行,再看一下其他情况   转载于:https://www.cnblogs.com/sanhao/p/10681919.html...

  • 第一:Windows 搜索神器:Everthing:免费,秒搜。对比:Beyond Compare:对比文件很方便。EverNote:记录各种笔记,可以分类。网页,手机端同步计事:百度云记事本,网页端方便登陆,手机端便于同步,及一些零散的小东西非常方便。缺点:排版不好看UltraEdit:列级模式,让你爽翻天。编程工具:Androi...