并发思想提炼(1)(理解并发,避免死锁)html
一直作服务器后端和基础组件平台开发,经常用到并发,故简单放些干货,一来算是总结,二来但愿后人少走弯路, 写到哪儿算哪儿,不按期更新。python
1. Introduction编程
先来明白一些概念。Concurrency并发和Multi-thread多线程不一样后端
你在吃饭的时候,忽然来了电话。安全
并发:表示多个任务同时执行。可是有可能在内核是串行执行的。任务被分红了多个时间片,不断切换上下文执行。服务器
多线程:表示确实有多个处理内核,可同时处理多个任务。多线程
世界的复杂的,世界是并发的。因而模拟这世上的业务的程序也是并发的。随着系统功能的增长,复杂度不断提升,并发特性被引入编程。架构
2. 最简单的并发并发
两个任务互不干扰,它们不会影响到系统的同一实体。每一个线程只须要本身作好本身的任务便可。线程
两个任务会影响到同一实体,可是不会在同时访问该同一实体。这样,在这个实体上,任务是串行执行的。这样也是安全的并发。
3. 危险的并发
两个任务同时访问同一实体,脏读写的问题,设有a值为1, 两个线程前后加1,按道理说最后a值结果为3.
两次操做后,a值不为3,而是2。这就是并发出现的错误。a如果一个可释放(disposable)的实体. T1线程释放a,T2线程操做a,会形成更大的错误。
4. 如何避免此危险
1. 干脆就串行执行T1,T2。不过没有利用处处理器的并发特性。虽然安全,可是效率不高。
2. T1,T2并发。可是不会同时操做同一个实体(Critical Entity)。即实体不是并发的。实体是串行的。
3. 读取关键实体使用Clone,拷贝出来的实体是临时的。本次操做在该临时实体上。下次操做继续Clone该实体再使用。
常常能用到的是方法2和方法3。接下来具体说说方法2和3。
5. 让对关键实体的访问串行
方法2的核心思想是串行。不过不是任务串行,而是访问共享实体时串行。串行是人类容易思惟的方式,把并发问题转变为顺序执行问题,也助于后来维护人员理解。一个最简单的实现方式就是加Lock then access。
6. Lock地狱
Lock是并发程序中经常使用的操做,每一个人都会用。。。。嗯。。。。经常会滥用。。。而后,运行一段时间。嘣,程序自爆。说说经常遇到的问题:
递归锁好解决,C++ 11中有std::recursive_mutex。再高级一点的语言自带这样的特性。好比C#中的lock就自带递归锁。通常地,递归锁经过里面添加counter实现,每次锁就counter++,每次release就counter--。Counter 为0就表示解锁。其实这就是一种信号量(semaphore)的实现方法。引伸开来去,C++中的share_ptr/auto_ptr就是此类思想,这种经过引用计数来判断该对象是否被使用,若检测到不被使用从而自动的release该段内存。C#和Java中内存管理就是这个思路。很少讲了,之后单开段子讲。
7. 死锁及其防止
确切的说死锁单纯在程序这个层面难以防止,最好在设计开始就注意这个问题。就是说在程序设计的时候就搞明白那些资源会互相调用。这样的状况就要多留心眼。我工做中一直写一些基础架构API。当其它工程师调用时。。。。。嗯。。。。。不仔细看文档,在一些不适用的地方使用形成了死锁,而后来找我。。。。。这种状况下要么就多培训,多强调使用手册。要么,这样说吧,把API写得健壮点。毕竟彻底不会知道用户会怎么使用。这就是我在API开发中使用静态语言的缘由(其实我更喜欢动态语言python,用python作leetcode真是心情舒畅),至少在编译阶段就能有必定程度的规则控制,而不是到了运行阶段报Error。这关系到如何编写健壮的API,之后开段子讲。
回到死锁防止这个问题,关于避免死锁的API可使用这个方法---try lock机制。简单就是说给lock一个时间锁,若是在一段时间内仍是没有取得该资源的访问权就跳过,放LOG,而后执行下面的步骤。能够经过前面讲的“等待信号量”来实现此机制,其实也不用本身特别实现,各主流语言应该都有。核心思想在软件层面上是放个自旋锁和Flag量,以为搞不明白(C++,C#,Java等该功能实现方式)的话本身也能够实现一个。