多线程(6)线程同步
使用多线程很容易,但是如果多个线程同时访问一个共享资源时而不加以控制,就会导致数据损坏。所以多线程并发时,必须要考虑线程同步(或称线程安全)的问题。
什么是线程同步
多个线程同时访问共享资源时,使多个线程顺序(串行)访问共享资源的机制。
注意:
1,共享资源,比如全局变量和静态变量。
2,访问,一般指写操作,读操作无需考虑线程同步。
3,串行,指当一个线程正在访问共享资源时,其它线程等待,直到该线程释放锁。
线程同步带来哪些问题
如果能保证多个线程不会同时访问共享资源,那么就不需要考虑线程同步。
虽然线程同步能保证多线程同时访问共享数据时线程安全,但是同时也会带来以下问题:
1,使用起来繁琐,因为必须找出代码中所有可能由多个线程同时访问的共享数据,并且要用额外的代码将这些代码包围起来,获取和释放一个线程同步锁,而一旦有一处忘记用锁包围,共享数据就会被损坏。
2,损害性能,因为获取和释放一个锁是需要时间的。
3,可能会造成更多的线程被创建,由于线程同步锁一次只允许一个线程访问共享资源,当线程池线程试图获取一个暂时无法获取的锁时,线程池就会创建一个新的线程。
所以,要从设计上尽可能地避免线程同步,实在不能避免的再考虑线程同步。
线程同步的常用解决方案
1,锁
包括lock关键字和Monitor类型。
使用lock关键字实现:
View Code需要注意的是:
1,lock锁定的对象必须是引用类型,不能是值类型。因为值类型传入会发生装箱,这样每次lock的将是一个不同的对象,就没有办法实现多线程同步了。
2,避免使用public类型的对象,这样很容易导致死锁。因为其它代码也有可能锁定该对象。
3,避免锁定字符串,因为字符串会被CLR暂留(也就是说两个变量的字符串内容相同,.net会把暂留的字符串对象分配给变量),导致应用程序中锁定的是同一个对象,造成死锁。
使用Monitor实现:
View Code完整代码:
View Code
lock关键字揭密:
通过查看lock关键字生成的IL代码,如下图:
从上图可以得出以下结论:
lock关键字内部就是使用Monitor类(或者说lock关键字是Monitor的语法糖),使用lock关键字比直接使用Monitor更好,原因有二。
1,lock语法更简洁。
2,lock确保了即使代码抛出异常,也可以释放锁,因为在finally中调用了Monitor.Exit方法。
2,信号同步
信号同步机制中涉及的类型都继承自抽象类WaitHandle,这些类型有EventWaitHandle(类型化为AutoResetEvent、ManualResetEvent)和Semaphore以及Mutex。关系如下图。
下面是使用信号同步机制的一个简单的例子,如下代码:

View Code运行效果:
1,线程阻塞,等待信号。

2,主线程发送信号,让线程继续执行。

3,线程安全的集合类
我们也可以通过使用.net提供的线程安全的集合类来保证线程安全。在命名空间:System.Collections.Concurrent下。
主要包括:
- ConcurrentQueue 线程安全版本的Queue【常用】
- ConcurrentStack线程安全版本的Stack
- ConcurrentBag线程安全的对象集合
- ConcurrentDictionary线程安全的Dictionary【常用】
漫思
浙公网安备 33010602011771号