“线程同步”的含义
当一个进程启动了多个线程时,如果需要控制这些线程的推进顺序(比如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(比较后再对整型或引用对象赋值),用于为整型或引用类型的赋值提供原子操作。
在大多数计算机上,增加变量操作不是一个原子操作,需要执行下列步骤:
- 将实例变量中的值加载到寄存器中。
- 增加或减少该值。
- 在实例变量中存储该值。
如果不使用 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......)
分享到:
相关推荐
Monitor类是.NET Framework中用于同步线程访问共享资源的一种方式。Monitor依靠内置锁实现线程同步,当一个线程访问某个对象时,它会尝试获取该对象上的锁。如果锁已经被其他线程获取,该线程会被阻塞,直到锁被释放...
Mutex(互斥锁)是一种高级同步机制,它允许只有一个线程访问共享资源,确保同一时间只有一个线程执行特定代码段。在C#中,Mutex类提供了WaitOne()和ReleaseMutex()方法,前者用于获取锁,后者用于释放锁。如果一个...
C#提供了多种机制来实现线程同步,包括lock关键字、Monitor、同步事件和等待句柄以及Mutex类。这些机制的主要目标是避免竞态条件,保证并发执行的线程能够正确地访问和修改共享数据。 首先,我们来看lock关键字。...
总的来说,`Mutex`是C#中实现多线程同步的重要工具,通过控制对共享资源的访问,确保了程序的正确性和稳定性。在编写多线程程序时,了解和掌握`Mutex`的使用是非常必要的。通过实践和理解,开发者可以更好地应对复杂...
在实际的多线程应用中,除了`lock`,还可以使用`Monitor`、`Mutex`、`Semaphore`等其他同步机制来实现互斥和资源限制。例如,`Mutex`允许跨进程的互斥访问,而`Semaphore`则可以控制同时访问资源的线程数量。 测试...
Mutex是一种全局资源锁,它允许在任何时间只有一个线程访问特定的资源或代码段。在C#中,我们可以使用Mutex的构造函数来创建一个互斥锁,并通过WaitOne()方法来获取锁,ReleaseMutex()方法来释放锁。当多个线程试图...
C#中的`lock`关键字是一种同步机制,可以防止多个线程同时访问共享资源。 4. **同步锁**:在描述中提到的“同步锁”是解决并发问题的关键。C#中的锁机制,如`Monitor`类、`Mutex`类和`Semaphore`类,可用于控制对...
C#提供了多种同步机制,如`lock`关键字实现互斥锁,`Monitor`类,`Mutex`和`Semaphore`用于系统级同步,以及`Monitor.Wait`和`Monitor.Pulse`用于等待和唤醒线程。 4. **线程间通信**:`WaitHandle`类(如`...
本文将深入探讨如何在C#中实现多线程同步并发操作,这不仅对于提高软件性能至关重要,也是高级程序员必须掌握的核心技能之一。 ### C#中的多线程同步并发操作 多线程编程可以极大地提高CPU的利用率,特别是在处理I...
内容概要:本文介绍了C#中常见的同步机制,用于解决多线程并发访问共享资源时的数据竞争和资源冲突问题。具体介绍了 lock 关键字、Monitor 类、Semaphore 和 SemaphoreSlim 类、Mutex 类、ReaderWriterLockSlim 类...
除了 lock 语句,C# 还提供了 Monitor 类来控制线程访问共享资源。Monitor 的 Enter 和 Exit 方法可以用来获取和释放锁: ```csharp Monitor.Enter(syncObject); try { // 访问或修改共享资源的代码 } finally { ...
为了保护应用程序的资源不被破坏,为多线程程序提供了三种加锁的机制,分别是:Monitor类、Lock关键字和Mutex类。 1. lock lock实现的功能是:使后进入的线程不会中断当前的线程,而是等待当前线程结束后再继续执行...
2. **线程同步与互斥**:为避免多线程间的竞态条件和数据不一致性,C#提供了多种同步机制,如`Mutex`、`Semaphore`、`Monitor`以及`lock`语句。这些机制用于控制对共享资源的访问,确保线程安全。源码中可能会展示...
本文将深入探讨C#中的线程概念、创建与管理线程的方法,以及线程访问控制的相关知识。 1. **线程基础** - **线程定义**:线程是进程中的一个执行单元,每个进程至少包含一个线程,负责执行程序代码。 - **主线程*...
在C#编程中,多线程同步是确保数据一致性与避免竞态条件的关键技术。`Monitor`类、`Lock`关键字(实际上是`System.Threading.Monitor`的隐式使用)和`Mutex`类提供了三种不同的机制来实现这一目标。让我们详细探讨...
C#提供了多种同步机制,如`Monitor`、`Semaphore`和`Mutex`等。其中,`Monitor`使用了.NET框架的锁对象,可以锁定特定代码块,保证同一时间只有一个线程执行。例如,`Monitor.Enter()`用于获取锁,`Monitor.Exit()`...
- `Mutex`:互斥量,一次只允许一个线程访问资源。 - `Semaphore`:信号量,限制同时访问资源的线程数量。 - `Monitor`:基于锁的对象,使用`lock`关键字实现。 - `Barrier`:屏障,用于等待一组线程到达特定点...
C#提供了一些同步机制,如Mutex、Semaphore、Monitor和锁(lock关键字),来确保对共享资源的访问是线程安全的。例如,使用lock关键字可以防止数据竞争: ```csharp lock (someObject) { // 访问共享资源的代码 } ...
- **Mutex**:互斥锁,同一时间只允许一个线程访问资源。 - **Semaphore**:信号量,限制同时访问资源的线程数量。 - **Monitor**:基于对象锁的同步,使用`lock`关键字实现。 - **EventWaitHandle**:事件,用于...
线程同步可以通过`Mutex`、`Monitor`、`Semaphore`、`ReaderWriterLockSlim`等工具实现。例如,`Mutex`可以实现全局资源的独占访问,防止多个线程同时访问同一资源。 3. **线程池**:`ThreadPool`是.NET中的一种...