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

简介:

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

2.同步线程

  • 手动同步

    • 监视器

    • 互斥

    • 可等待事件

 

同步线程

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

 

2.1 手动同步

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

2.1.1 监视器

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

2.1.1-1

1
2
3
4
5
6
7
8
9
10
  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
2
3
4
5
6
7
8
9
10
11
  1 ManualSynchronization monitorcase =  new  ManualSynchronization();
 
  3 Monitor.Enter(monitorcase);
  try
  5 {
  6     monitorcase.DoSomeThing();
  7 }
  finally
  9 {
10     Monitor.Exit(monitorcase);
11 }

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

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

2.1.1-2

1
2
3
4
5
6
7
8
9
10
  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
2
3
4
5
6
7
8
9
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
2
3
4
5
1 ManualSynchronization monitorcase =  new  ManualSynchronization();
lock (monitorcase)
3 {
4     monitorcase.DoSomeThing();
5 }

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

2.1.1-4

1
2
3
4
5
6
7
8
9
10
11
12
13
  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
2
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
2
3
4
5
6
7
8
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
  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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
  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
2
3
4
5
1      public  enum  EventResetMode
2     {
3        AutoReset,
4        ManualReset
5     }

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

1
2
3
4
5
6
7
8
9
10
  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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
  1      public  class  EventDom:IDisposable
  2     {
  3         ManualResetEvent _WaitHandle;
  4          public  EventDom()
  5         {
  6             _WaitHandle =  new  ManualResetEvent( true );
 
  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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
  1      public  class  EventDom : IDisposable
  2     {
  3         AutoResetEvent _WaitHandle;
  4          public  EventDom()
  5         {
  6             _WaitHandle =  new  AutoResetEvent( true );
 
  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     }

首先把手动重置类型换成了自动重置类型,然后再测试代码中把设置状态为未发送的方法,改成了设置状态为已发送的方法。

这个结果是正确,因为自动重置类型就是事件状态被设置为信号已发,它就会保持这个状态,直到某个线程从等待调用中释放出来,然后在这个时候,它的状态会发生改变,自动的反转到未发送状态。

还有一些扩展的知识点就不在这一一阐述了,希望本篇能对大家有所帮助。END

 

 



     本文转自jinyuan0829 51CTO博客,原文链接:http://blog.51cto.com/jinyuan/1421966,如需转载请自行联系原作者



相关文章
|
6月前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
455 0
|
5月前
|
安全
List并发线程安全问题
【10月更文挑战第21天】`List` 并发线程安全问题是多线程编程中一个非常重要的问题,需要我们认真对待和处理。只有通过不断地学习和实践,我们才能更好地掌握多线程编程的技巧和方法,提高程序的性能和稳定性。
354 59
|
8月前
|
Java 开发者
解锁并发编程新姿势!深度揭秘AQS独占锁&ReentrantLock重入锁奥秘,Condition条件变量让你玩转线程协作,秒变并发大神!
【8月更文挑战第4天】AQS是Java并发编程的核心框架,为锁和同步器提供基础结构。ReentrantLock基于AQS实现可重入互斥锁,比`synchronized`更灵活,支持可中断锁获取及超时控制。通过维护计数器实现锁的重入性。Condition接口允许ReentrantLock创建多个条件变量,支持细粒度线程协作,超越了传统`wait`/`notify`机制,助力开发者构建高效可靠的并发应用。
129 0
|
5月前
|
安全 Java
线程安全的艺术:确保并发程序的正确性
在多线程环境中,确保线程安全是编程中的一个核心挑战。线程安全问题可能导致数据不一致、程序崩溃甚至安全漏洞。本文将分享如何确保线程安全,探讨不同的技术策略和最佳实践。
97 6
|
5月前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
141 8
|
5月前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
5月前
|
开发框架 Java .NET
.net core 非阻塞的异步编程 及 线程调度过程
【11月更文挑战第12天】本文介绍了.NET Core中的非阻塞异步编程,包括其基本概念、实现方式及应用示例。通过`async`和`await`关键字,程序可在等待I/O操作时保持线程不被阻塞,提高性能。文章还详细说明了异步方法的基础示例、线程调度过程、延续任务机制、同步上下文的作用以及如何使用`Task.WhenAll`和`Task.WhenAny`处理多个异步任务的并发执行。
|
5月前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
6月前
|
Java
【编程进阶知识】揭秘Java多线程:并发与顺序编程的奥秘
本文介绍了Java多线程编程的基础,通过对比顺序执行和并发执行的方式,展示了如何使用`run`方法和`start`方法来控制线程的执行模式。文章通过具体示例详细解析了两者的异同及应用场景,帮助读者更好地理解和运用多线程技术。
84 1
|
6月前
.NET 4.0下实现.NET4.5的Task类相似功能组件
【10月更文挑战第29天】在.NET 4.0 环境下,可以使用 `BackgroundWorker` 类来实现类似于 .NET 4.5 中 `Task` 类的功能。`BackgroundWorker` 允许在后台执行耗时操作,同时不会阻塞用户界面线程,并支持进度报告和取消操作。尽管它有一些局限性,如复杂的事件处理模型和不灵活的任务管理方式,但在某些情况下仍能有效替代 `Task` 类。
下一篇
oss创建bucket