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

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

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

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

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

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

图1:

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

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

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

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

 [MethodImpl(MethodImplOptions.Synchronized)]
        public void ShowMessage()
        {
            for (int i = 0; i < 10; i++)
            {
                if (i == 5)
                    Monitor.Wait(this);//等第二个线程使用完后,我在继续执行。将当前线程放置在等待队列里
                Thread currentthread = Thread.CurrentThread;
                Console.WriteLine(currentthread.Name + "|" + currentthread.ManagedThreadId + "|" + i.ToString());
            }
        }
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void PluseThread()
        {
            for (int i = 0; i < 10; i++)
            {
                Thread currentthread = Thread.CurrentThread;
                Console.WriteLine(currentthread.Name + "|" + currentthread.ManagedThreadId + "|" + i.ToString());
            }
            Monitor.Pulse(this);//将等待队列里的线程放到锁定队列,也就是Monitor.Enter();
        }

 

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

Myclass1 myclass = new Myclass1();

            ThreadStart startdeleted = new ThreadStart(myclass.ShowMessage);
            Thread thread = new Thread(startdeleted);
            thread.Name = "线程1";
            ThreadStart stra = new ThreadStart(myclass.PluseThread);
            Thread thread2 = new Thread(stra);
            thread2.Name = "线程2";

            thread.Start();
            thread2.Start();

            thread2.Join();
            Console.WriteLine("线程2已经释放");
            thread.Join();
            Console.WriteLine("线程1已经释放");

            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
            Console.ReadLine();

 

在调用的代码里面,大概意思是这样的,我们同时开启两个线程,入口点分别是上面的两个方法,在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互斥体

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

 

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

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

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

            Myclass1 myclass = new Myclass1();

            Thread thread = new Thread(new ThreadStart(myclass.Print));
            thread.Name = "线程1";

            Thread thread2 = new Thread(new ThreadStart(myclass.Print));
            thread2.Name = "线程2";

            thread.Start();
            thread2.Start();

            thread2.Join();
            thread.Join();
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

 

图3:

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

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

public class EventWaitHandlerDemo
    {
        ManualResetEvent resetevent = new ManualResetEvent(false);
        public EventWaitHandlerDemo()
        {
            Thread thread = new Thread(new ThreadStart(this.DoWork));
            thread.Start();
        }
        private void DoWork()
        {
            int count = 0;
            while (true)
            {
                resetevent.WaitOne();
                Console.WriteLine(count++);
            }
        }
        public void GoThread()
        {
            resetevent.Set();
            Console.WriteLine("线程启动");
        }
        public void Stopthread()
        {
            resetevent.Reset();
            Console.WriteLine("线程暂停");
        }
        public void Close()
        {
            resetevent.Close();
            Console.WriteLine("线程终止");
        }
    }

 

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

            EventWaitHandlerDemo demo = new EventWaitHandlerDemo();
            demo.GoThread();

            Thread.Sleep(1000);
            demo.Stopthread();

            Thread.Sleep(1000);
            demo.Close();

            Console.WriteLine("程序结束");

 

图4:

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

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

 public class AutoResetEventDemo
    {
        AutoResetEvent autoevent = new AutoResetEvent(true);
        public AutoResetEventDemo()
        { }
        public void Print()
        {
            autoevent.WaitOne();
            //  autoevent.Reset();在手动事件中,需要手动切换状态
            int i = 0;
            while (true)
            {
                if (i == 10)
                {
                    Console.WriteLine(Thread.CurrentThread.Name + "线程结束");
                    autoevent.Set();
                    break;
                }
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "|" + i++);
            }
        }
    }

 

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

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

            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
            AutoResetEventDemo autodemo = new AutoResetEventDemo();

            Thread thread1 = new Thread(new ThreadStart(autodemo.Print));
            thread1.Name = "线程1";

            Thread thread2 = new Thread(new ThreadStart(autodemo.Print));
            thread2.Name = "线程2";
            thread1.Start();
            thread2.Start();

            thread1.Join();
            thread2.Join();
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
            Console.Read();

 

图5:

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

 

 

时间: 2024-10-09 06:34:23

.NET简谈组件程序设计之(手动同步)的相关文章

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

我们继续学习.NET多线程技术,这篇文章的内容可能有点复杂.在打破常理之后,换一种新的思考模型最为头疼.这篇文章里面会涉及到一些不太常见的概念,比如:上下文.同步域等等.我也是最近才接触这些关于组件编程方面的高深技术,大家一起学习,再大的困难也是有时间限制的,只要我们坚持. 在本人的上一篇文章".NET简谈组件程序设计之(多线程与并发管理一)"中,只是初步的带领我们学习一下关于多线程的一些基本的原理,包括线程切换,线程的开始.执行.等待.结束. 这篇文章的重点是学习关于线程的同步.互斥

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

在本人的上一篇文章".NET简谈组件程序设计之(初识序列化.持久化) "中,我们基本上了解了什么叫序列化和持久化.通过系统为我们提供的服务,我们可以很方便的进行二进制序列化.SOAP协议序列化. 今天这篇文章是来讲解怎么运用一些高级的功能,在序列化.反序列化过程中进行一些控制.[王清培版权所有,转载请给出署名] 这里穿插一句题外话:其实在我们自己编写组件的时候真的有好多东西可以借鉴.NET平台的一些优点,它的功能都不是死的,可以订阅.可以切入,在我们编写组件的时候,我们其实也要好好考虑

.NET简谈组件程序设计之(初识.NET线程Thread)

由于多线程的内容比较多我会用几篇文章来讲解. 多线程在我们日常开发过程中用的很多,上一篇".NET简谈组件程序设计之(异步委托) "详细的讲解了基于委托的多线程使用,委托是基于后台线程池的原理,这篇文章将主要介绍直接使用Thread对象来实现多线程. 当然使用Thread没有使用Delegate那么容易,毕竟多线程跟异步调用是两个相差很大的技术方向,我也是略懂点皮毛,在此献丑给大家,如有讲的不对的地方还请指出.[王清培版权所有,转载请给出署名] 我们先来理解几个概念,以方便我们学习.

.NET简谈组件程序设计之(初识NetRemoting)

在本人的".NET简谈组件程序设计之(初识远程调用)  "一文中,我们了解到什么是远程调用或者说在.NET平台上远程调用是什么样子的,可能和偏低层(Socket\Rpc)的远程调用有点距离.这只是系统为我们封装了假象而已,看不见不代表没有这逻辑,是为我们减轻了劳动负担.[王清培版权所有,转载请给出署名] 这篇文章我们来简单的了解一下在.NET平台上有一个强有力的远程调用武器,也是上一篇文章中我一笔带过的远程英雄.NetRemoting. 其实在.NET平台里面到处都能看见Remotin

.NET简谈组件程序设计之(初识远程调用)

在.NET1.0版本出来的时候,要想进行远程调用基本上都是通过WebService的方式.而随着.NET2.0版本的出现,我们可以通过一个更加方便且高扩展性的框架来进行编写远程调用的程序,也就是我们都比较熟悉的.NetRemoting. 网上对.NetRemoting技术讲解的文章不计其数,但是很少有一本比较全面的.系统的学习书籍.我们都是从哪些零散的知识里慢慢摸索,效果不太理想. 今天我也来简单的介绍一下我理解的Remoting.不仔细研究一下还真不知道它的厉害,完全的托管平台.高扩展性.灵活

.NET简谈组件程序设计之(初识序列化、持久化)

 今天我们来学习在组件开发中经常用到的也是比较重要的技术"序列化". 序列化这个名词对初学者来说不太容易理解,有点抽象.我们还是用传统的分词解释吧,肯定能搞懂它的用意是什么. 解释:数学上,序列是被排成一列的对象(或事件):这样,每个元素不是在其他元素之前,就是在其他元素之后.这里,元素之间的顺序非常重要. 那么我们对照这样的解释来分析一下我们程序中的序列化什么意思.都知道对象的状态是在内存中实时存着的,对象的状态在初始化的时候是通过系统分配的,在后期的程序运行过程中可能对它进行过一些

.NET简谈组件程序设计之(delegate与event关系)

 本人最近一段时间在学习关于.NET组件编程方面的技术,在学习过程中确实有很多好的东西需要与大家分享.[王清培版权所有,转载请给出署名] 关于什么叫组件编程,其实就是利用.NET来开发基于组件模型的程序,面向组件编程而非面向对象编程,这是一个高度,没有很长时间的学习与磨练 是体会不到这个感觉的.我们现在的开发思想应该是以面向对象为主的,但是如何提升这个高度,只有慢慢的学习了. 其实面向组件编程包含了面向对象编程思想,将一组功能独立的封装起来供以后重复使用,但是在开发组件的过程中需要使用到面向对象

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

在本人的上一篇文章中只是简单的介绍了一下.NETRemoting的一般概念和基本的使用.这篇文章我想通过自己的学习和理解将对.NETRemoting的整体的一个框架进行通俗的讲解,其中最重要的就是信道(管道)处理模型思想,这里面蕴含了很多的设计原理.[王清培版权所有,转载请给出署名].NETRemoting远程处理架构是一个半成品,是.NET给我们的扩展框架,要想用于商业项目必须进行一些安全.性能方面的控制.要想进行一定深度的扩展那就要必须了解它的整体结构,各个点之间的关系才能很好的控制它. 网

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

最近在苦学.NET底层框架模型,发现.NET深入真的不是一般的难,不开源.没有相关系统的官方的书籍做学习资料,只能零散的看MSDN.要想摸熟.NET的模型真的并非易事.慢慢来吧.[王清培版权所有,转载请给出署名] .NET应用程序域(AppDomain)是我们所有.NET应用程序的逻辑宿主容器.初次接触会感觉到AppDomain离我们日常开发比较远,不常用到.其实是我们很少接触一些复杂而底层的系统结构.在日常的开发中,我们多数是基于数据库的管理信息系统(MIS),做增.删.改.查的操作.我始终认