谈谈多线程同步相关概念

多进程如何同步

多进程由于会对原进程状态进行复制,因此就不存在过多的联系,如果要实现进程间通信,可选择信号、IPC、管道、UNIX域文件、网络

匿名管道只能实现父子进程之间的通信,命名管道则可以实现不同进程之间通信

信号只能作为一种触发机制或有限信息提示机制,局限性比较大

IPC由系统提供,主要是消息队列、信号量数组、共享内存,但不使用描述符,因此不支持多路转接

UNIX域文件和网络socket,描述符实现,支持多路转接

管道可以支持全双工(半双工也可以通过两个实现全双工),由操作系统提供同步,只需要读写即可

信号由于进程pending,连续的信号一般不能实现连续触发,除非有信号队列,但其依赖系统实现

IPC虽然能够实现消息同步,但由于不支持多路转接,效率不会特别高

UNIX域与网络socket,最佳的多进程同步工具,但需要额外的实现代码

多线程同步

多线程只是创建新的线程栈和私有数据,其它均与原进程的主线程共享

共享的数据读操作无副作用,但如果是修改(写或删)则会对其它线程造成副作用

因此需要一定的同步机制来阻止这种副作用产生

常见的有mutex、condition、semaphore、barrier、rwlock、event、rlock

Mutex

mutex又称互斥锁,即一个线程获取锁,则其它线程在获取时会被阻塞(睡眠),只有锁被释放后其它线程才能竞争锁

互斥锁的几个问题,线程释放锁后如果又立即获得锁怎么办,如果有过多的线程竞争同一个锁怎么办

前者根据线程调度策略和对线程设置一定限制来实现(刚释放的线程短时间不能再获取),如Golang的Mutex设计了一种饥饿状态

后者是设计问题,要么采取其它的锁机制,要么减小锁的粒度(减少锁被获取到释放的间隔),要么不要允许创建过多的线程参与竞争

简单来说,mutex实现了对资源的读写限制,但是比较原始

Condition

condition又称条件变量,一般实现是基于一个mutex和某个判断条件,需要先获取锁,然后循环判断条件,如果不符合,则调用等待(等待这个操作的实现会自动释放锁,当获得通知时,会自动抢锁)

条件变量的底层实现做到了非忙等,通过类似信号的机制通知唤醒

条件变量的问题,仍然是mutex的问题

简单来说,condition在mutex的基础上,增加了条件判断机制,使得可以根据条件的变化通知相应线程工作,且不是忙等轮询

Semaphore

semaphore又称信号量,最早是Dijkstra提出的,是一种古老的同步原语,本质是设定一个量值,只允许同时存在指定量值的线程同时工作

针对的资源不是数据,而是工作线程数,也就是说信号量限制了处理能力

Barrier

barrier直译为屏障,一般用于实现阶段性任务的并发

如每个阶段的任务必须全部完成后才能进入下一个阶段,通过屏障可以对阶段任务执行并发,但又不会阻碍将所有任务流程合并在一起

一开始指定一个量值,然后在代码中每个阶段完成的时候调用等待并休眠,只有给定量值的线程运行到等待处时所有线程才会停止休眠继续控制流的执行

使用屏障最需要注意一个问题,即线程数量与给定量值必须匹配,其次是线程崩溃的处理(一个线程崩溃会导致无法执行到等待,需要相应的恢复机制)

RWLock

rwlock又称读写锁,主要针对mutex的特殊场景做优化

如果程序中读操作比写操作多,那么读写锁更适用该场景

读写锁允许读锁可被多次获取,但要获取写锁必须等所有读锁被释放,且一旦获取写锁,那么所有读锁获取操作被阻塞

RLock

rlock又称可重入锁,即对于单个线程,允许循环获取锁,但必须有匹配的释放锁操作数

Event

event又称事件锁,类似条件变量,但条件内置(一个内置bool变量作为事件)

所有等待事件的线程被阻塞,直到事件被设置为True,此后可以将事件清空,清空后再执行到等待时还是会阻塞

SpinLock

spin又称自旋锁,机制同mutex,即一个线程获取,其他线程都被阻塞,一般用于内核中

由于mutex阻塞会导致进程睡眠,不适合快速上下文情况,如果一个共享资源只会被持有很短时间,那么自旋锁更合适

自旋锁不睡眠,而是循环获取,因此不适合长期持有共享资源的情况,会造成忙等

死锁问题

一般发生在两个或以上的锁竞争之间,通用的解决方案是确保锁获取的顺序,其次是提高设计能力

posted @ 2020-06-16 18:15  Miaynak  阅读(217)  评论(0编辑  收藏  举报