1.在单线程中,我们每次只能做一件事情。
而在多线程中,其实最本质的也是一次只能做一件事情,只是CPU给定的时间片完成之后,切换到另一个线程,然后这个分配CPU的时间片完成之后,再次切换到另一个线程,如此反复,其间切换的速度很快,给人一种同时进行的错觉而已。
但在多线程中,往往会发生两个线程抢占资源的问题,所以我们需要防止这些资源访问的冲突。
C#提供线程同步机制来防止资源访问的冲突,其中主要用到lock关键字、Monitor类和Mutex类
2.线程同步机制
应用程序中使用多线程的一个好处是每个线程都可以异步执行。
线程同步是指并发线程高效、有序地访问共享资源所采用的技术。
所谓同步,是指某一时刻只有一个线程可以访问资源,只有当资源所有者主动放弃了代码或资源的所有权时,其他线程才可以使用这些资源
3.使用lock关键字实现线程同步
lock关键字可以用来确保代码块完成运行,而不会被其他线程中断,它是通过在代码块运行期间为给定对象获取互斥锁来实现。
lock语句以关键字lock开头,它有一个作为参数的对象,在该参数的后面还有一个一次只能有一个线程执行的代码块。
语法格式:
Object thisLock = new Object();
lock(thisLock){//要运行的代码块}
提供给lock语句的参数只是用来唯一标识由多个线程共享的资源,所以可以使任意类实例,实际上,此参数通常标识需要进行线程同步的资源。
避免锁定public类型或不受应用程序控制的对象实例。尽量避免锁定公共数据类型,锁定字符串尤其危险。所以,最好锁定不会被暂留的私有对象或受保护的对象。
说明:lock语句使用Monitor类实现,等效于try/finally语句块,使用lock关键字通常比直接使用Monitor类更可取。首先,lock更简洁;其次,lock确保了及时受保护的代码引发异常,也可以释放基础监听器,这是通过finally关键字来实现,无论是否引发一场,它都执行关联的代码块。
注意:如果在静态方法中使用lock关键字,则不能使用this
static void Main(string[] args) { lock(new Program()) { Console.WriteLine("锁定线程"); Console.ReadLine(); } }
4.使用Monitor驱动对象实现线程同步
Monitor类提供了同步对对象的访问机制,通过向单个线程授予对象锁来控制对对象的访问。对象锁提供限制访问代码块(临界区)的能力。当一个线程拥有对象锁时,其他任何线程都不能获取该锁。
Monitor类的主要功能:
(1)根据需要与某个对象相关联
(2)它是未绑定的,可以直接从任何上下文调用它
(3)不能创建Monitor类的实例
Monitor类的常用方法:
Enter 在指定的对象上获取排他锁
Exit 释放指定对象上的排他锁
Pulse 通知等待队列中的线程锁定对象状态的更改
PulseAll 通知所有的等待线程对象状态的更改
TryEnter 试图获取指定对象的排他锁
Wait 释放对象上的锁并阻止当前线程,直到它重新获取该锁
使用Monitor类锁定的是对象(引用类型)而不是值类型
实例代码:
class Program { static void Main(string[] args) { Program myProgram = new Program(); //实例化类对象 myProgram.LockThread(); //调用锁定线程方法 Console.ReadLine(); } void LockThread() { Monitor.Enter(this); //锁定当前线程 Console.WriteLine("锁定线程以实现线程同步"); Monitor.Exit(this); //释放当前线程 } }
5.使用Mutex类实现线程同步
Mutex类与监视器类似,与监视器不同的是,Mutex类可以用来是跨进程的线程同步。
使用WaitHandle.WaitOne方法请求互斥体的所属权
拥有互斥体的线程可以在对WaitOne方法的重复调用中请求相同的互斥体而不会阻止其执行,但是记住,线程必须要调用同样多次的ReleaseMutex方法来释放互斥体的所属权。
Mutex类强制线程标识,因此互斥体只能由获得它的线程释放。
Mutex类常用的方法
Close 在派生类中被重写时,释放由当前WaitHandle持有的所有资源
OpenExisting 打开现有的已命名的互斥体
ReleaseMutex 释放Mutex一次
SignalAndWait 原子操作的形式,向一个WaitHandle发出信号并等待另一个
WaitAll 等待指定数组中的所有元素都收到信号
WaitAny 等待指定数组中的任一元素收到信号
WaitOne 在派生类中重写,阻止当前线程,知道当前WaitHandle收到信号
实现Mutex类线程同步的步骤:
1.创建一个Mutex对象,构造函数中比较常用的有public Mutex(bool initallyOwned)
参数制定了创建该对象的线程是否希望立即获取其所有权,当在一个资源得到保护的类中创建Mutex对象,常常将该参数设置为false
2.在需要单线程访问的地方调用其等待方法,等待方法请求Mutex对象的所有权。如果该所有权被另一个线程拥有,则阻塞请求线程,并放到等待队列中,请求线程将保持阻塞,直到Mutex对象受到其所有者线程发出的将其释放的信号为止。
6.实例代码
创建程序,自定义lockThread方法,利用Mutex对象的WaitOne方法阻止当前线程,然后再调用Mutex对象的ReleaseMutex方法释放Mutex对象,即释放当前线程。最后在Main方法中通过对象调用lockThread方法
class Program { static void Main(string[] args) { Program myProgram = new Program(); //实例化类对象 myProgram.LockThread(); //调用锁定线程方法 Console.ReadLine(); } void lockThread() { Mutex myMutex = new Mutex(false); //创建Mutex对象 myMutex.WaitOne(); //阻止当前线程 Console.WriteLine("锁定线程以实现线程同步"); myMutex.ReleaseMutex(); //释放Mutex对象 } }
总的来说,线程同步的东西还是要多读几遍,才能明白。而且要不断地实践锻炼才能知道lock,Monitor类和Mutex类是怎么用的,各自的区别优点以及缺点在哪里。
感觉学无止境啊,继续Fighting吧