目录
- 1.术语
- 2.买票案例
- 3.互斥锁的本质
- 4.可重入与线程安全
- 5.死锁
- 6.总结
前言
进程间通信告诉我们,两个进程要想进行通信,就必须先看到一份临界资源。而对于临界资源的操作,管道是自带同步与互斥机制的。
假若我们对于一份临界资源不加某种限制的话,两个线程同时对其进行操作时,就可能会发生数据不一致的问题。
来举个例子:
今有一全局变量tickets,作为临界资源;有2个线程对于tickets进行操作:tickets++,假设tickets初始为0
thread1 执行tickets++时,会发生以下的事件:
- tickets从内存拷贝到cpu寄存器
- cpu++ tickets
- cpu寄存器写回内存
但是我们知道,线程每次将数据放入cpu寄存器并进行运算的时候,都会有一个时间片,如果时间片到了,那么寄存器会保存上下文,该线程进入等待队列,等待下一次的载入寄存器。
当我们的thread1完成了++之后,此时时间片恰好到了,寄存器里的值(1)没有写回到内存,而此时,thread2来了,thread2看到的内存中的值并不是++后的(1)而是++前的(0),所以,thread2从0加到1,并把1写回到内存。
此时thread2时间片到了;thread1来了,thread1恢复上下文,并把1写回到内存。
最终,thread1和thread2虽然各++了一次,但是内存中的tickets值却是1
归根结底,造成这一切的罪魁祸首是thread1 执行cnt++还没结束,就因为时间片到了被切出去了,然后thread2对于临界资源cnt进行了修改
线程切换的时机:内核态返回用户态(比如进行系统调用就会发生内核态用户态的切换)
我们必须引入互斥量,对于临界资源的操作进行一定的限制
1.术语
- 临界资源:多线程执行流共享的资源就叫做临界资源
- 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
- 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
- 原子性:不会被任何调度机制打断的操作,该操作只有两态:
- 要么完成
- 要么未完成
互斥量mutex
- 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。
- 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。
- 多个线程并发的操作共享变量,会带来一些问题。
2.买票案例
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#define NUM 5
using namespace std;
int tickets = 1000;
void* routine(void* args) {
while(true) {
if(tickets > 0) {
usleep(1000);
printf(&