当前位置: 移动技术网 > IT编程>开发语言>.net > .NET组件程序设计之线程、并发管理(二)

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

2017年12月25日  | 移动技术网IT编程  | 我要评论

奉贤二严寺,踩过界tvb,万千星辉贺台庆2012

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

 

2.同步线程

 

手动同步

 

监视器

 

互斥

 

可等待事件

 

 

 

同步线程

 

所有的.NET组件都支持在多线程的环境中运行,可以被多个线程并发访问,如果没有线程同步,这样的后果是当多个线程同时访问 对象状态时,对象的状态可能被破坏,造成不一致性。.NET提供了两种方法来避免这样的问题,使得我们设计的组件更加健壮。 第一种是自动同步,让你使用一个属性来修饰组件,这样就可以把组件交给.NET了,同步的事情也就交给了.NET。 第二种是手动同步,这是让你使用.NET提供的同步对象来实现线程同步,也不是太复杂,本篇将会对手动同步来稍作讲解。

 

 

 

2.1 手动同步

 

    .NET手动同步提供了一套丰富的同步锁,上一节说到同步域,同步域事实上是一个巨大的宏锁,而手动同步则提供了 对被锁对象的细粒度控制,可以控制访问对象、单一成员甚至是单行的代码。这样的好处就是有可能的提高系统的性能和吞吐量。

 

2.1.1 监视器

 

监视器是一种只能和引用类型一块工作的锁。

 

2.1.1-1

 

复制代码

 1     public class ManualSynchronization

 2     {

 3         public void DoSomeThing()

 4         {

 5             for (int i = 0; i < 100; i++)

 6             {

 7                 Console.WriteLine(i.ToString());

 8             }

 9         }

10     }

复制代码

复制代码

 1 ManualSynchronization monitorcase = new ManualSynchronization();

 2 

 3 Monitor.Enter(monitorcase);

 4 try

 5 {

 6     monitorcase.DoSomeThing();

 7 }

 8 finally

 9 {

10     Monitor.Exit(monitorcase);

11 }

复制代码

任何线程的任何对象都可以调用Enter()方法来锁定对象,如果Monitor正在被一个线程使用,而这个时候又有一个线程来请求对象Enter(),这样就会使第二个线程阻塞,直到第一个线程调用Exit(),如果这时有多个线程请求对象Enter(),它们就会被放置在一个叫做锁队列的队列里,并依照队列的顺序获得服务顺序。

 

你还可以使用Monitor类为静态类方法或静态属性提供安全线程访问,方法是让Monitor锁定该类型,而不是一个实例:

 

2.1.1-2

 

复制代码

 1     public class ManualSynchronization

 2     {

 3         public static void SDoSomeThing()

 4         {

 5             for (int i = 0; i < 100; i++)

 6             {

 7                 Console.WriteLine(i.ToString());

 8             }

 9         }

10     }

复制代码

复制代码

1            Monitor.Enter(typeof(ManualSynchronization));

2            try

3            {

4                ManualSynchronization.SDoSomeThing();

5            }

6            finally

7            {

8                Monitor.Exit(typeof(ManualSynchronization));

9            }

复制代码

在C#中为了简化这样的写法,提供了lock语句,使编译器在try/finally语句中自动产生对Enter()和Exit()的调用。

 

比如你写下这样的代码等同于2.1.1-1的示例代码:

 

2.1.1-3

 

1 ManualSynchronization monitorcase = new ManualSynchronization();

2 lock(monitorcase)

3 {

4     monitorcase.DoSomeThing();

5 } 

像上面的代码这样写看似没什么问题了,因为这个lock所定对象实例或者是对象类型,是根据客户端开发者的判断而定的,这样的锁定方式与客户端耦合度大,看下以下代码:

 

2.1.1-4

 

复制代码

 1     public class ManualSynchronization

 2     {

 3         public void DoSomeThing()

 4         {

 5             lock (this)

 6             {

 7                 for (int i = 0; i < 100; i++)

 8                 {

 9                     Console.WriteLine(i.ToString());

10                 }

11             }

12         }

13     }

复制代码

1 ManualSynchronization monitorcase = new ManualSynchronization();

2 monitorcase.DoSomeThing();

这样感觉是不是舒服不少,这就是方法同步了,.NET内部也对它提供了支持,定义在System.Runtime.CompilerServices命名空间里的MethodImpl方法属性接受一个MethodImplOptions类型的枚举。其中一个枚举值是MethodImplOptions.Synchronized。当运行这个枚举值的时候,编辑器就指示.NET运行时在方法入口锁定对象,语义和2.1.1-4的代码断相同:

 

2.1.1-5

 

复制代码

1     public class ManualSynchronization

2     {

3         [MethodImpl( MethodImplOptions.Synchronized)]

4         public void DoSomeThingSynchroniezd()

5         {

6             Console.WriteLine("studycase");

7         }

8     } 

复制代码

2.1.2 互斥

 

这一个小节要讲到的是Mutex类,它是从WaitHandle派生的类,它保证了各个线程在某个资源或代码块上相互排斥。

 

 2.1.2-1

 

复制代码

 1     public class MutexDom:IDisposable

 2     {

 3         public MutexDom(){}

 4         private int _Num = 0;

 5         public int Num

 6         {

 7             get

 8             {

 9                 return _Num;

10             }

11             set

12             {

13                 _Num = value;

14             }

15         }

16         public void Dom()

17         {

18                  for (int i = 0; i < 100; i++)

19                 {

20                     Num = Num + i;

21                     Console.WriteLine(Thread.CurrentThread.Name + "_" + Num.ToString() +"_"+Thread.CurrentThread.ManagedThreadId.ToString());

22                 }

23 

24             

25         }

26         public void Dispose()

27         {

28          

29         }

30 

31         public static void Test()

32         {

33             MutexDom mutexDom=new MutexDom();

34             ThreadStart threadStart=new ThreadStart(mutexDom.Dom);

35             Thread thread1 = new Thread(threadStart);

36             thread1.Name = "Thread_One";

37             Thread thread2 = new Thread(threadStart);

38             thread2.Name = "Thread_Two";

39             thread1.Start();

40             thread2.Start();

41         }

42     }

复制代码

MutexDom.Test();启动测试,我所希望的效果是Dom()方法是有序的执行的,而我用了一个int类型的Nun属性来作为计数器,那我们就一起来看一下结果吧(可能每次运行结果不一样)

 

 

 

我所期望的在线程Thread_One中执行0递增至99的值时4950,而在结果中已经超出了这个范围,这说明了什么?说明了两个线程在交替的对Num进行操作。修改一下代码,再来看一下:

 

 2.1.2-2

 

复制代码

 1     public class MutexDom:IDisposable

 2     {

 3         private Mutex _Mutex;

 4         public MutexDom()

 5         {

 6             _Mutex = new Mutex();

 7         }

 8         private int _Num = 0;

 9         public int Num

10         {

11             get

12             {

13                 return _Num;

14             }

15             set

16             {

17                 _Num = value;

18             }

19         }

20         public void Dom()

21         {

22             _Mutex.WaitOne();//如果当前资源被占用 则等待占用它的线程发送消息

23             try

24             {

25                 for (int i = 0; i < 100; i++)

26                 {

27                     Num = Num + i;

28                     Console.WriteLine(Thread.CurrentThread.Name + "_" + Num.ToString() +"_"+Thread.CurrentThread.ManagedThreadId.ToString());

29                 }

30             }

31             finally

32             {

33                 _Mutex.ReleaseMutex();

34             }

35             

36         }

37         public void Dispose()

38         {

39             _Mutex.Close();

40         }

41 

42         public static void Test()

43         {

44             MutexDom mutexDom=new MutexDom();

45             ThreadStart threadStart=new ThreadStart(mutexDom.Dom);

46             Thread thread1 = new Thread(threadStart);

47             thread1.Name = "Thread_One";

48             Thread thread2 = new Thread(threadStart);

49             thread2.Name = "Thread_Two";

50             

51             thread1.Start();

52             thread2.Start();

53             

54         }

55     }

复制代码

 

 

从结果中得出,是线程Thread_Two先执行的,这个没关系,只要看它的结果值就行了,这就说明了,在线程"Thread_Two"执行对Dom()方法操作的时候"Thread_One"是肯定已经启动了的,而且是在等待"Thread_Two"的释放消息,这样就保持了对象状态的一致性,这个时候"Thread_One"是在一个等待队列中的。如果这个时候"Thread_One"调用ReleaseMutex()方法,是会报错的,因为ReleaseMutex()方法是只能当前所占有的线程来进行释放,互斥就这样完成了。

 

2.1.3 可等待事件

 

EventWaitHandle类派生于WaitHandle,被用于跨线程通知事件。 它有两种状态:信号已发状态、信号未发状态。 Set()方法和 Reset()方法分别把句柄状态设置为信号已发或信号未发。 它有两种使用方式,一种是手动重置,还有一种是自动重置。是通过给构造函数提供一个EventResetMode类型的枚举值,

 

1     public enum EventResetMode

2     {

3        AutoReset,

4        ManualReset

5     }

.NET提供了EventWaitHandle的两个强类型子类,定义如下:

 

复制代码

 1     public class ManualResetEvent:EventWaitHandle

 2     {

 3         public ManualResetEvent(bool initialState):base(initialState,EventResetMode.ManualReset)

 4         {}

 5     }

 6     public sealed class AutoResetEvent : EventWaitHandle

 7     {

 8         public AutoResetEvent(bool initialState):base(initialState,EventResetMode.AutoReset)

 9         {}

10     }

复制代码

先来看一下手动重置:

 

2.1.3-1

 

复制代码

 1     public class EventDom:IDisposable

 2     {

 3         ManualResetEvent _WaitHandle;

 4         public EventDom()

 5         {

 6             _WaitHandle = new ManualResetEvent(true);

 7 

 8             Thread thread = new Thread(DoWork);

 9             thread.Start();

10         }

11         private void DoWork()

12         {

13             int num = 0;

14             while (true)

15             {

16                 _WaitHandle.WaitOne();

17                 num++;

18                 Console.WriteLine("EventDom_" + num.ToString());

19             }

20         }

21         public void StartThread()

22         {

23             _WaitHandle.Set();

24             Console.WriteLine("EventDom->StartThread");

25         }

26         public void StopThread()

27         {

28             _WaitHandle.Reset();

29             Console.WriteLine("EventDom->StopThread");

30         }

31         public void Dispose()

32         {

33             _WaitHandle.Close();

34         }

35 

36         public static void Test()

37         {

38             EventDom eventDom = new EventDom();

39             eventDom.StopThread();

40         }

41 

42      }

复制代码

 调用EventDom.Test();进行测试,结果如下图:

 

 

 

在构造函数中我就已经把手动重置事件声明为了 信号已发状态,所以在运行的时候,while在每次循环的时候等待接收到的信号一直都是已发送状态,所以是一直在输出,直到调用了StopThread()方法中的Reset()方法,把状态设置为未发送状态,才使执行暂停。

 

再来看一下自动重置,修改一下上段的代码,

 

复制代码

 1     public class EventDom : IDisposable

 2     {

 3         AutoResetEvent _WaitHandle;

 4         public EventDom()

 5         {

 6             _WaitHandle = new AutoResetEvent(true);

 7 

 8             Thread thread = new Thread(DoWork);

 9             thread.Start();

10         }

11         private void DoWork()

12         {

13             int num = 0;

14             while (true)

15             {

16                 _WaitHandle.WaitOne();

17                 num++;

18                 Console.WriteLine("EventDom_" + num.ToString());

19             }

20         }

21         public void StartThread()

22         {

23             _WaitHandle.Set();

24             Console.WriteLine("EventDom->StartThread");

25         }

26         public void StopThread()

27         {

28             _WaitHandle.Reset();

29             Console.WriteLine("EventDom->StopThread");

30         }

31         public void Dispose()

32         {

33             _WaitHandle.Close();

34         }

35 

36         public static void Test()

37         {

38             EventDom eventDom = new EventDom();

39             eventDom.StartThread();

40         }

41     }

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网