互斥的相关概念
- 临界资源:多线程执行流共享的资源就叫做临界资源
- 临界区:访问临界资源的代码,叫临界区
- 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区访问临界资源,通常对临界资源起保护作用
- 原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成
互斥锁:保证多个执行流在访问临界资源时,是原子操作
加锁的本质是让所有的线程串行化
线程是可以在任何时间被切换,线程执行到临界区代码时被切换,是不会有其他线程进入临界区的
其他线程线程必须先申请锁
线程被切走,是带着锁走的
互斥锁原理
一条汇编语句就是一条原子性语句
寄存器中的数据,全部是线程内部上下文的数据,一个线程从CPU上被剥离走,必须带走自己的上下文数据
一个线程申请到了锁,被切换后,锁也就被拿走了
互斥锁底层是一个互斥量,互斥量的本质是计数器,值保存在内存中,计数器值只能为1(可以获取互斥锁),和0(不能获取互斥锁);
- 互斥锁刚初始化出来的值是1,表示线程可以加锁,然后互斥锁计数器就从1变为0;
- 其01的转化操作不是加减,而是将计数器的值和寄存器中的值,靠一条汇编指令将两者里的值进行交换;
- 程序计数器的值可为0为1,而寄存器的值必须初始化为0;
- 只需要判断交换完毕之后,寄存器中的值为1则加锁成功,为0则加锁失败
互斥锁接口
初始化接口:
加锁接口:一般使用阻塞加锁
解锁接口:
- 不管用上述三个任何一个加锁,都能用该函数解锁
int pthread_mutex_unlock(pthread_mutex_t* mutex);
参数是互斥锁变量的地址
线程和互斥锁的创建顺序
加锁位置:在临界资源前加锁
解锁位置:在所有可能导致线程退出的地方进行解锁
若没有20行和23行解锁代码
- 假设线程A加锁完毕,未被加锁的三个线程等待加锁,
- A运行完毕再次请求加锁,此时该锁已经被使用,线程A也会被阻塞,请求加锁
- 使用pstack命令并不能看出哪个线程使用锁后再请求加锁
查找方法:使用gdb调试
gdb attch +pid
调式存在的进程,attch可以换成-p
bt
查看堆栈信息
thread apply all bt
查看所有线程堆栈信息
现在要查找哪个线程使用了锁,就需要进入线程的堆栈;
由此可见是5696号线程使用了锁后,还想进行加锁操作
- 所以需要进行解锁(上图代码中,break语句前后都是解锁操作);
- 并且在所有可能导致线程退出的地方都应进行解锁(原因以及在代码部分标注)