c# Thread5——线程同步之基本原子操作。Mutex互斥量的使用
之前的博文也说到了如果多线程对于访问的公共资源操作都是原子操作,那么可以避免竞争条件。关于多线程的竞争可以百度。
1.执行最基本的原子操作
c#提供了一系列供我们使用的原子操作的方法和类型,比如我们的自增和自减操作。
看代码
class Program { private static int _count = 0; static void Main(string[] args) { var thread1 = new Thread(() => { for(int i = 0; i < 100000; i++) { Operation(); } }); var thread2 = new Thread(() => { for (int i = 0; i < 100000; i++) { Operation(); } }); thread1.Start(); thread2.Start(); thread1.Join(); thread2.Join(); Console.WriteLine(_count); Console.Read(); } public static void Operation() { //自增 _count ++; _count--; }
上面的代码还是会引起竞争条件问题,所以并不是线程安全的,我们可以使用c#提供的Interlocked进行自增,自减等操作,并且是原子性的。
对上面程序进行改动
private static int _count = 0; static void Main(string[] args) { var thread1 = new Thread(() => { for(int i = 0; i < 100000; i++) { Operation(); } }); var thread2 = new Thread(() => { for (int i = 0; i < 100000; i++) { Operation(); } }); thread1.Start(); thread2.Start(); thread1.Join(); thread2.Join(); Console.WriteLine(_count); Console.Read(); } public static void Operation() { //自增 Interlocked.Increment(ref _count); Interlocked.Decrement(ref _count); }
结果就是正确的了。
2.Metux类(互斥量)
Mutex是一种原始的在线程同步方式,它只对一个线程授予共享资源的独占访问,并且它是操作系统全局的,所以它更大的场景用于不同程序之间多线程的同步
它的几个构造方法在上图中。
Mutex():这是比较特殊的,用的很少的构造方法,因为之前说Mutex是一个操作系统全局的互斥量,但是需要根据名字来使用,这种构造方法是匿名的,所以可以称作局部互斥量。
Mutex(bool initiallyOwned):传入的bool值表示创建者是否立刻拥有该互斥量
Mutex(bool initiallyOwned,string name):与上面的区别就是拥有名字,可以在去他程序中被使用。
Mutex(bool initiallyOwned,string name,out bool createNew):第三个out参数用于表明是否获得了初始的拥有权。这个构造函数应该是我们在实际中使用较多的。
Mutex(bool initiallyOwned,string name,out bool createNew,MuteSecurity muteSecurity):第四个参数是对于Mutex的一些安全设置,详见MSDN
说这么多我们先来一个例子来看看如何使用Mutex
private static readonly string MutexName = "GLOBAL_MUTEX"; static void Main(string[] args) { var thread = new Thread(() => { Operations(); }); thread.Start(); Operations(); Console.ReadLine(); } public static void Operations() { bool result; using (var mutex = new Mutex(false, MutexName,out result)) { try { if (mutex.WaitOne(TimeSpan.FromSeconds(5), false)) { Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} Instance is running"); Thread.Sleep(2000); Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} Instance is Exiting"); } } catch { } finally { mutex.ReleaseMutex(); } } }
这段代码其实就是看主线程和子线程谁先拿到互斥量,就可以先执行,另外一个线程就会等待它执行完再去执行。所以结果如下。
我们稍微改动一下代码。
private static readonly string MutexName = "GLOBAL_MUTEX"; static void Main(string[] args) { var thread = new Thread(() => { Operations(); }); thread.Start(); Operations(); Console.ReadLine(); } public static void Operations() { bool result; using (var mutex = new Mutex(false, MutexName,out result)) { try { if (mutex.WaitOne(TimeSpan.FromSeconds(5), false)) { Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} Instance is running"); Thread.Sleep(6000); Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} Instance is Exiting"); mutex.ReleaseMutex(); } else { Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} Instance is waitting out of time"); } } catch { Console.WriteLine("error"); } finally { } } }
增加一点时间延迟,因为我们设置了5s的等待,但是主体时间超过6s,所以必将会有线程等待超时。
那么可能会有人问了,那他和上一节的monitor到底有何区别?其实最大的区别也提到过了,就是它是操作系统全局的。具体细节可以参考园子里这篇文章,写的比我详细很多。
https://www.cnblogs.com/suntp/p/8258488.html
最后就是因为Mutex对象是操作系统全局的,一定要正确关闭释放它。