目录
更新记录
version | status | description | date | author |
---|---|---|---|---|
V1.0 | C | Create Document | 2019.1.15 | John Wan |
status:
C―― Create,
A—— Add,
M—— Modify,
D—— Delete。
注:内核版本 3.0.15
1、概念
1.1 并发概念
并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。其中两种并发关系分别是同步和互斥。
1.2 同步和互斥
互斥:所谓互斥,是指分布在不同进程之间的若干程序片断,当某个进程运行其中一个程序片段时,其它进程就不能运行它们之中的任一程序片段,只能等到该进程运行完这个程序片段后才可以运行。
同步:所谓同步,是指分布在不同进程之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。
互斥:是某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:即在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
显然,同步是一种更为复杂的互斥,而互斥是一种特殊的同步。也就是说互斥是两个线程之间不可以同时运行,他们会相互排斥,必须等待一个线程运行完毕,另一个才能运行,而同步也是不能同时运行,但他是必须要安照某种次序来运行相应的线程(也是一种互斥)!
1.3 同步和异步
同步和异步关注的是消息通信机制。
同步(synchronization):所谓同步,就是在发出一个 “调用” 时,在没有得到结果之前,该 “调用” 就不返回,但一旦调用返回,就得到结果。换句话说,就就由 “调用者” 主动等待这个 “调用” 的结果。
异步(asynchronous):异步则相反,“调用” 发出之后,这个 “调用” 就直接返回了,但没有结果,继续执行别的。而 “调用者” 得到结果,是在“被调用者” 有了结果之后通过状态、通知等方式再来通知 “调用者”,或通过回调函数处理这个 “调用”。
举例:
打电话问书店老板有没有《分布式系统》这本书。
如果是同步通信机制,书店老板会说,“你稍等,我查一下”,然后开始查啊查,你那电话那边等啊等,等查好了(可能是5秒,可能是一天)告诉你结果(返回结果),然后你才去干别的。
而异步通信机制,书店老板会说,“我查一下,查好了我再告诉你”,然后把电话挂了,老板开始查啊查,你继续干你的事。等到老板查好了,他主动来告诉你结果。至于通过什么方式来告诉你,就有多种选择。例如可以通过 “回电” 这种方式来回调。
1.4 阻塞与非阻塞
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。
阻塞调用:是指得到调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会恢复。
非阻塞调用:是指不能立刻得到结果之前,该调用不会阻塞当前线程(不会挂起当前线程)。
举例:
打电话问书店老板有没有《分布式系统》这本书。
阻塞式调用,你会在打电话等待结果的过程中,把自己 “挂起” (CPU不会在该进程耗费资源),直到得到结果,才会恢复(重新占用CPU资源)。
非阻塞式调用,打了电话,你不管老板有没有告诉你,你自己先一边去玩了(CPU还是会在你的这个进程上耗费资源),并且你如果想要得到结果,那么过一段时间就要check一下老板有没有返回结果。
阻塞与非阻塞的设置:
fd = open("...", O_RDWR | O_NONBLOCK);
文件读写操作的默认方式:阻塞方式。
应用程序员可通过使用 O_NONBLOCK
标志来设置读写操作为非阻塞方式。( 该标志定义在 < linux/fcntl.h > 中,在打开文件时指定 )
在这里阻塞、非阻塞与是否同步、异步无关,所以不要将其两两结合。老板通过什么方式回答你与结果是怎样的也无关。
1.5 竞态
Linux系统是多任务的,很多任务会同时执行。例如:
有三个执行单元ABC,共享了内存资源。执行单元 A 对 Buffer 写1000个“a”;执行单元 B 对 Buffer 写1000个“b”;执行单元 C 从 buffer 中读取数据。我们希望按照 A写→C读→B写→C读 这样规定的次序运行,那么就提供一套机制来实现这样的运行,不然如果 A写→B写→C读,执行单元 C 读出来的就混乱了。当然有比这个更复杂,更混乱的并发操作存在设备驱动中,只要有多个进程对共享资源的同时访问,就可能出现竞态。
以下三种情况会导致竞态:
1)对称多处理的多个CPU
2)单CPU内进程和抢占它的进程
3)中断和进程
竞态的解决方法:
解决竞态的途径是 “保证对共享资源的互斥访问” ,也就是一个执行单元在访问共享资源的时候,其它的执行单元被禁止访问。访问共享资源的代码区称为临界区,临界区需要使用互斥机制来保护。
1.6 Linux 中实现互斥的方法
原子操作、自旋锁、信号量、互斥体等。
2、原子操作
所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。
举例:以decl (递减指令)为例
这是一个典型的 "读-改-写" 过程,涉及两次内存访问。设想在不同CPU运行的两个进程都在递减某个计数值,可能发生的情况是:
⒈ CPU A(CPU A上所运行的进程,以下同)从内存单元把当前计数值⑵装载进它的寄存器中;
⒉ CPU B从内存单元把当前计数值⑵装载进它的寄存器中。
⒊ CPU A在它的寄存器中将计数值递减为1;
⒋ CPU B在它的寄存器中将计数值递减为1;
⒌ CPU A把修改后的计数值⑴写回内存单元。
⒍ CPU B把修改后的计数值⑴写回内存单元。
可以看到,内存里的计数值应该是 0,然而它却是1。如果该计数值是一个共享资源的引用计数,每个进程都在递减后把该值与0进行比较,从而确定是否需要释放该共享资源。这时,两个进程都去掉了对该共享资源的引用,但没有一个进程能够释放它--两个进程都推断出:计数值是1,共享资源仍然在被使用。
为避免以上情况的出现,那么在 "读-改-写" 的整个过程中,不允许被打断。
2.1 常用函数
atomic_t v = ATOMIC_INIT(0); //定义原子变量v并初始化为0
atomic_read(atomic_t *v); //返回原子变量的值
void atomic_inc(atomic_t *v); //原子变量增加1
void atomic_dec(atomic_t *v); //原子变量减少1
int atomic_dec_and_test(atomic_t *v); //自减操作后测试其是否为0,为0则返回true,否则返回false。
2.2 案例
参考:【韦东山】嵌入式 Linux 视频教程第一期—第12课第7节。
3、信号量
信号量(Semaphore),是在多线程环境下用来保证两个或多个关键代码段不被并发调用的措施。在进入一个关键代码段之前,线程必须先申请获取一个信号量;关键代码段执行完之后,那么该线程必须释放信号量。当申请获取信号量时,被其它进程已经占用,必须等待直到占用的线程释放信号量。
信号量有计数与互斥之分,计数则是设定该共享资源同时最多能被多少个线程使用。而互斥则属于其中的特例,具有排他性,只能同时被一个线程使用。
3.1 常用函数
//定义信号量
struct semaphore sem;
//初始化信号量
void sema_init (struct semaphore *sem, int val);
void init_MUTEX(struct semaphore *sem);//初始化为0
static DECLARE_MUTEX(button_lock); //定义互斥锁
//获得信号量
void down(struct semaphore * sem);
int down_interruptible(struct semaphore * sem);
int down_trylock(struct semaphore * sem);
//释放信号量
void up(struct semaphore * sem);
3.2 案例
参考:【韦东山】嵌入式 Linux 视频教程第一期—第12课第7节。
参考
- 并发,同步,异步,互斥,阻塞,非阻塞的理解
- 迅为开发板资料—linux驱动教程—视频26
- 【韦东山】嵌入式 Linux 视频教程第一期—第12课第7节。
- 关于同步、异步与阻塞、非阻塞的理解