在CS架构中项目中,线程的使用就成了无可避免的。在使用线程时,线程安全如何处理,以及如何避免死锁?要解决这些问题,那么我们需要了解,什么是线程安全,什么是死锁?
什么是线程安全?
线程安全问题其实是指多个线程对于某个共享资源的访问导致的原子性、可见性和有序性问的问题,而这些问题会导致共享数据存在一个不可预测性,使得程序在执行的过程中会出现一些超预期的结果
通俗来说就是多线程同时运行,如果每次运行的结果和单线程运行的结果一致,那么就是线程安全的。
什么是非线程安全?
引发线程安全的原因是,多个线程可以同时操作全局变量/共享变量/静态变量/磁盘文件/数据库的值就可能存在线程安全问题,因为多个线程操作,出现了覆盖。List集合也是非线程安全的。
如何避免线程不安全?
解决线程不安全的方式是增加同步锁,这里笔者用过的是lock锁,由于导致线程安全问题的根本原因是多线程并行访问共享资源,对共享资源加锁以后,多个线程在访问这个资源的时候必须要先获得锁
也就是先获得访问资格,而同步锁的特征是在同一个时刻只允许一个线程访问该资源,直到锁被释放。这个方式可以有效的解决线程安全问题,但是同时带来的是加锁和释放锁所产生的性能开销,因为加锁
会涉及到用户空间到内核空间的一个转换以及上下文切换,尽量去减少共享对象的使用从业务上实现隔离避免并发。另外在使用lock锁的时候也需要注意
Lock锁
概念
解决多线程冲突问题,Lock是语法糖,Monitor.Enter,占据一个引用,别的线程就只能等着。锁的本质就是把多线程在某些特定场合下变成单线程,来完成某些特定操作。
在日常工作中,有些是特定需要锁的,更多的时候我们可以把数据拆分,避免多线程操作同一个数据,这样既安全又高效。例如有10000个任务,那可以进行拆分,某个线程执行1-100给任务。
Lock锁不允许的类型
String:string在内存分配上是重用的,会冲突。这么话应该这么理解,比如你定义一个变量name=”张三”,再定义一个变量temeName=”张三”,那么在C#内存分配时会指向同一个引用。
Null:可以运行,不能编译通过。
详解Lock
Lock锁的两钟方式:
Lock(this)
不推荐的锁
如果只是当前内部实例使用,则不会冲突,
外面如果也要用实例,就冲突了
标准锁
微软推荐的标准锁
private static readonly object Form_Lock = new object();
死锁:
一组互相竞争资源的线程因为互相等待,导致"永久"阻塞的现象
发生死锁的原因:
1.互斥条件
共享资源x和y只能被一个线程占用
2占有性等待
线程t1已经占有了共享资源x,在等待共享资源y的时候不释放共享资源x
3不可抢占
其他线程不能强行占有线程t1占有的资源
4循环等待
线程t1等待线程t2占有的资源,线程t2等待线程t1占有的资源
如何避免死锁?
既然我们知道了死锁的原因是需要同时满足以上4个条件,那我们只要不同时满足四个条件即可避免死锁的发生,而在四个条件中,互斥条件是无法避免的,因为锁的本质就是通过互斥来解决线程安全问题。
对于占有性等待,我们可以一次性申请所有资源,这样就不存在等待了。对于不可抢占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源,这样不可抢占的条件就被破坏掉了
对于循环等待可以按序申请资源来进行预防