`
tansitongba
  • 浏览: 504853 次
文章分类
社区版块
存档分类
最新评论

[C#学习笔记之多线程2]多线程同步与并发访问共享资源工具—Lock、Monitor、Mutex、Semaphore

 
阅读更多

“线程同步”的含义


当一个进程启动了多个线程时,如果需要控制这些线程的推进顺序(比如A线程必须等待B和C线程执行完毕之后才能继续执行),则称这些线程需要进行“线程同步(thread synchronization)”。

线程同步的道理虽然简单,但却是给多线程开发带来复杂性的根源之一。当线程同步不好时,有可能会出现一种特殊的情形——死锁(Dead Lock)


“死锁”的含义


死锁表示系统进入了一个僵化状态,所有线程都没有执行完毕,但却谁也没法继续执行。究其根源,是因为“进程推进顺序不当”和“资源共享”。如例:

1)进程推进顺序不当造成死锁

在该例中,主线程mainThread先开始执行,然后启动线程ta,线程ta执行结束前又要等待mainThread线程执行结束,这样就出现了“交叉等待”的局面,必然死锁!


2)共享资源造成死锁

所谓“共享资源”,指的是多个线程可以同时访问的数据结构、文件等信息实体。

在该例中,线程th1执行时先申请使用R1,然后再申请使用R2,而线程th2执行时先申请R2,然后再申请R1,这样对于线程th1和th2,就会造成各自拥有一个对方需要的资源部释放,而又同时申请一个对方已经占有的资源,必然会造成死锁。


多线程数据存取错误


当多个线程访问同一个数据时,如果不对读和写的顺序作出限定,例如一个线程正在读而另一个数据尝试写,则读数据的线程得到的数据就可能出错。这也是多线程带来的问题。如例:

四个线程同时读写共享变量ShareResource.Count,由于未对读写进行控制,所以必然会造成数据存取错误!

线程同步与并发访问控制手段


正如为了解决车辆交通问题,人们建立了红绿灯的交通控制手段一样,可以为线程设定一套控制机制,以实现线程间的同步,以及保证以正确的顺序来访问共享资源。为了保护应用程序的资源不被破坏,为多线程程序提供了三种加锁的机制,分别是:Monitor类、Lock关键字和Mutex类。

1、Monitor类


(1)使用方法


  • Monitor对象的Enter方法可用于向共享资源申请一把“独占锁”。当一个线程拥有特定共享资源的独占锁时,尝试访问同一共享资源的其他线程只能等待。
  • Monitor对象的Exit方法用于释放锁。
  • 要注意:Enter与Exit方法必须严格配对,否则,有可能出现死锁情况。
  • Monitor可以锁定单个对象,也可以锁定一个类型的静态字段或属性
1).Monitor.Enter(共享资源对象);
2).Monitor.Enter(typeof(共享资源类型));
Monitor类的使用模板:

Monitor.Enter(共享资源对象); //申请对象锁

//得到了对象锁,可以对共享资源进行访问,
//其他线程只能等待
//访问共享资源
//对共享资源的访问完成,释放对象锁,
//让其他线程有机会访问共享资源

Monitor.Exit(obj);

(2)Monitor的特殊注意之处:


Monitor一般只用于访问引用类型的共享资源,如果将其施加于值类型变量,则值类型变量将会被装箱,而当调用Exit方法时,虽然是同一个值类型变量,但实际上此值类型变量又会被第二次装箱,这将导致Enter方法所访问的对象与Exit方法所访问的不是同一个,Monitor对象将会引发SynchronizationLockException。
因此,不要将Monitor用于值类型!

(3)Monitor.Wait()和Monitor.Pulse()


Wait()释放对象上的锁,以便允许其他线程锁定和访问该对象。在其他线程访问对象时,调用线程将等待。
Pulse(),PulseAll()向一个或多个等待线程发送信号。该信号通知等待线程锁定对象的状态已更改,并且锁的所有者准备释放该锁。等待线程被放置在对象的就绪队列中以便它可以最后接收对象锁。一旦线程拥有了锁,它就可以检查对象的新状态以查看是否达到所需状态。PulseAll与Pulse方法类似,不过它是向所有在阻塞队列中的进程发送通知信号,如果只有一个线程被阻塞,那么请使用Pulse方法。
注意:Pulse、PulseAll和Wait方法必须从同步的代码块内调用。
例1:

例2:
可能的执行结果:
当threadPing进程进入ThreadPingProc锁定ball并调用Monitor.Pulse( ball )后,它通知threadPong从阻塞队列进入准备队列,当threadPing调用Monitor.Wait( ball )阻塞自己后,它放弃了了对ball的锁定,所以threadPong得以执行。
因此,可以借助Monitor.Pulse()来控制进程的推进顺序。

2、Lock关键字

C#使用Lock关键字来简化Monitor的用法。lock就是对Monitor的Enter和Exit的一个封装,而且使用起来更简洁,因此Monitor类的Enter()和Exit()方法的组合使用可以用lock关键字替代。

lock(obj)
{
//访问共享资源代码段
}
等价于:
Monitor.Enter(obj);
//访问共享资源代码段
Monitor.Exit(obj);


3、自旋锁SpinLock


当一个线程需要访问共享资源时,它可以调用SpinLock.Enter或SpinLock.TryEnter方法申请独占锁,如果暂时不能获得锁(这时可能运行于另一个CPU核上的线程正在访问共享资源),当前线程就会“空转”若干个时钟周期,然后再次尝试。在这个过程中,线程的状态仍是Running,从而避免了操作系统进行一次线程上下文切换所带来的开销。


4、实现原子操作——Interlocked类


Interlocked类是一种互锁操作,提供对多个线程共享的变量进行同步访问的方法,互锁操作具有原子性,即整个操作时不能由相同变量上的另一个互锁操作所中断的单元。
这个类提供了Increment、Decrement、Add静态方法用于对int或long型变量的递增、递减或相加操作。还提供了Exchange(为整型或引用对象赋值)、CompareExchange(比较后再对整型或引用对象赋值),用于为整型或引用类型的赋值提供原子操作。
在大多数计算机上,增加变量操作不是一个原子操作,需要执行下列步骤:
  1. 将实例变量中的值加载到寄存器中。
  2. 增加或减少该值。
  3. 在实例变量中存储该值。
如果不使用 Increment 和 Decrement,线程会在执行完前两个步骤后被抢先。 然后由另一个线程执行所有三个步骤。 当第一个线程重新开始执行时,它覆盖实例变量中的值,造成第二个线程执行增减操作的结果丢失。

利用Interlocked类类解决生产者-消费者关系中的竞争条件问题:(例子来自《周长发——c#面向对象编程》

5、Mutex类


Mutex与Monitor类似,需要注意的是Mutex分两种:一种是本地Mutex一种是系统级Mutex,系统级Mutex可以用来进行跨进程间的线程的同步。尽管 mutex 可以用于进程内的线程同步,但是使用 Monitor 通常更为可取,因为监视器是专门为 .NET Framework 而设计的,因而它可以更好地利用资源。相比之下,Mutex 类是 Win32 构造的包装。尽管 mutex 比监视器更为强大,但是相对于 Monitor 类,它所需要的互操作转换更消耗计算资源。
一个线程要想访问共享资源,它必须调用Mutex对象的Wait系列方法之一提出申请。当申请得到批准的线程完成了对于共享资源的访问后,它调用Mutex对象的ReleaseMutex()方法释放对于共享资源的访问权。

利用多线程模拟3个人在ATM上多次提款操作:
可能的运行结果:

Mustex与Monitor有一个很大的区别:
Mutex可以用来同步属于不同应用程序或者进程的线程,而Monitor没有这个能力。

为了说明这个区别,我们将生产者和消费者线程分别放在两个应用程序中,在两个应用程序中都各自创建一个同名的Mutex对象,并利用他们来对生产者和消费者线程同步:
生产者线程所在应用程序代码:
消费者线程所在应用程序代码:

我们分别编译这两个文件,然后运行Mutex1,他会在另一个窗口中启动Mutex2,可能的结果如下:



6、Semaphore


Semaphore可以限制可同时访问某一资源或资源池的线程数。
Semaphore类在内部维护一个计数器,当一个线程调用Semaphore对象的Wait系列方法时,此计数器减一,只要计数器还是一个正数,线程就不会阻塞。当计数器减到0时,再调用Semaphore对象Wait系列方法的线程将被阻塞,直到有线程调用Semaphore对象的Release()方法增加计数器值时,才有可能解除阻塞状态。

示例说明:
图书馆都配备有若干台公用计算机供读者查询信息,当某日读者比较多时,必须排队等候。UseLibraryComputer实例用多线程模拟了多人使用多台计算机的过程

可能的运行结果:

(2012/5/31 22:51 It's time to go to sleep......)睡觉

分享到:
评论

相关推荐

    C#中使用Monitor类、Lock和Mutex类来同步多线程的执行.pdf

    Monitor类是.NET Framework中用于同步线程访问共享资源的一种方式。Monitor依靠内置锁实现线程同步,当一个线程访问某个对象时,它会尝试获取该对象上的锁。如果锁已经被其他线程获取,该线程会被阻塞,直到锁被释放...

    C# 多线程的同步与互斥(使用Mutex和Event)

    Mutex(互斥锁)是一种高级同步机制,它允许只有一个线程访问共享资源,确保同一时间只有一个线程执行特定代码段。在C#中,Mutex类提供了WaitOne()和ReleaseMutex()方法,前者用于获取锁,后者用于释放锁。如果一个...

    c# 线程同步: 详解lock,monitor,同步事件和等待句柄以及mutex

    C#提供了多种机制来实现线程同步,包括lock关键字、Monitor、同步事件和等待句柄以及Mutex类。这些机制的主要目标是避免竞态条件,保证并发执行的线程能够正确地访问和修改共享数据。 首先,我们来看lock关键字。...

    C# 使用Mutex实现多线程同步实例

    总的来说,`Mutex`是C#中实现多线程同步的重要工具,通过控制对共享资源的访问,确保了程序的正确性和稳定性。在编写多线程程序时,了解和掌握`Mutex`的使用是非常必要的。通过实践和理解,开发者可以更好地应对复杂...

    C#多线程互斥实例 多线程获取同一变量

    在实际的多线程应用中,除了`lock`,还可以使用`Monitor`、`Mutex`、`Semaphore`等其他同步机制来实现互斥和资源限制。例如,`Mutex`允许跨进程的互斥访问,而`Semaphore`则可以控制同时访问资源的线程数量。 测试...

    C# 多线程同步与互斥,使用Mutex和AutoResetEvent类

    Mutex是一种全局资源锁,它允许在任何时间只有一个线程访问特定的资源或代码段。在C#中,我们可以使用Mutex的构造函数来创建一个互斥锁,并通过WaitOne()方法来获取锁,ReleaseMutex()方法来释放锁。当多个线程试图...

    C#多线程读写sqlite

    C#中的`lock`关键字是一种同步机制,可以防止多个线程同时访问共享资源。 4. **同步锁**:在描述中提到的“同步锁”是解决并发问题的关键。C#中的锁机制,如`Monitor`类、`Mutex`类和`Semaphore`类,可用于控制对...

    C#多线程开发之并发编程经典实例.zip

    C#提供了多种同步机制,如`lock`关键字实现互斥锁,`Monitor`类,`Mutex`和`Semaphore`用于系统级同步,以及`Monitor.Wait`和`Monitor.Pulse`用于等待和唤醒线程。 4. **线程间通信**:`WaitHandle`类(如`...

    C#实现多线程同步并发操作

    本文将深入探讨如何在C#中实现多线程同步并发操作,这不仅对于提高软件性能至关重要,也是高级程序员必须掌握的核心技能之一。 ### C#中的多线程同步并发操作 多线程编程可以极大地提高CPU的利用率,特别是在处理I...

    C#多线程并发访问资源的冲突解决方案

    内容概要:本文介绍了C#中常见的同步机制,用于解决多线程并发访问共享资源时的数据竞争和资源冲突问题。具体介绍了 lock 关键字、Monitor 类、Semaphore 和 SemaphoreSlim 类、Mutex 类、ReaderWriterLockSlim 类...

    c# 多线程 同步问题解决

    除了 lock 语句,C# 还提供了 Monitor 类来控制线程访问共享资源。Monitor 的 Enter 和 Exit 方法可以用来获取和释放锁: ```csharp Monitor.Enter(syncObject); try { // 访问或修改共享资源的代码 } finally { ...

    C#中的lock、Monitor、Mutex学习笔记

    为了保护应用程序的资源不被破坏,为多线程程序提供了三种加锁的机制,分别是:Monitor类、Lock关键字和Mutex类。 1. lock lock实现的功能是:使后进入的线程不会中断当前的线程,而是等待当前线程结束后再继续执行...

    c#多线程编程实战(原书第二版)源码

    2. **线程同步与互斥**:为避免多线程间的竞态条件和数据不一致性,C#提供了多种同步机制,如`Mutex`、`Semaphore`、`Monitor`以及`lock`语句。这些机制用于控制对共享资源的访问,确保线程安全。源码中可能会展示...

    C#多线程C#线程及访问杂记

    本文将深入探讨C#中的线程概念、创建与管理线程的方法,以及线程访问控制的相关知识。 1. **线程基础** - **线程定义**:线程是进程中的一个执行单元,每个进程至少包含一个线程,负责执行程序代码。 - **主线程*...

    C#中使用Monitor类、Lock和Mutex类来同步多线程的执行[收集].pdf

    在C#编程中,多线程同步是确保数据一致性与避免竞态条件的关键技术。`Monitor`类、`Lock`关键字(实际上是`System.Threading.Monitor`的隐式使用)和`Mutex`类提供了三种不同的机制来实现这一目标。让我们详细探讨...

    C#的多线程示例;几个多线程之间的互斥,同步;WPF主界面INVOKE

    C#提供了多种同步机制,如`Monitor`、`Semaphore`和`Mutex`等。其中,`Monitor`使用了.NET框架的锁对象,可以锁定特定代码块,保证同一时间只有一个线程执行。例如,`Monitor.Enter()`用于获取锁,`Monitor.Exit()`...

    C# 多线程实例多线程实例多线程实例

    - `Mutex`:互斥量,一次只允许一个线程访问资源。 - `Semaphore`:信号量,限制同时访问资源的线程数量。 - `Monitor`:基于锁的对象,使用`lock`关键字实现。 - `Barrier`:屏障,用于等待一组线程到达特定点...

    C#多线程执行

    C#提供了一些同步机制,如Mutex、Semaphore、Monitor和锁(lock关键字),来确保对共享资源的访问是线程安全的。例如,使用lock关键字可以防止数据竞争: ```csharp lock (someObject) { // 访问共享资源的代码 } ...

    C#多线程学习 Thread类使用 线程等编程方法

    - **Mutex**:互斥锁,同一时间只允许一个线程访问资源。 - **Semaphore**:信号量,限制同时访问资源的线程数量。 - **Monitor**:基于对象锁的同步,使用`lock`关键字实现。 - **EventWaitHandle**:事件,用于...

    C#.NET多线程实例6个(包括多线程基本使用,多线程互斥等全部多线程使用实例),可直接运行

    线程同步可以通过`Mutex`、`Monitor`、`Semaphore`、`ReaderWriterLockSlim`等工具实现。例如,`Mutex`可以实现全局资源的独占访问,防止多个线程同时访问同一资源。 3. **线程池**:`ThreadPool`是.NET中的一种...

Global site tag (gtag.js) - Google Analytics