这里写目录标题
- 同步概念
- 线程同步概念
- 数据混乱原因
- 互斥量
- 原理
- 锁的注意事项
- 1、cpu时间轮片
- 2、建议锁
- 总结
- 使用锁来管理线程同步
- 问题产生
- 主要函数
- init、destory
- lock、unlock
- 代码
- 注意事项
- 条件变量
- 二级目录
- 二级目录
- 二级目录
- 信号量
- 二级目录
- 二级目录
- 二级目录
- 一级目录
- 二级目录
- 二级目录
- 二级目录
同步概念
线程同步概念
数据混乱原因
互斥量
原理
多个线程操作共享区时,每个线程访问时,会加上一把锁,只允许某单个线程对共享区进行操作,操作完之后,再解锁,表示允许其他线程访问
而这个锁就叫互斥锁,这个互斥锁只有一把,各个线程争抢这把锁,谁先拿到锁,谁先有操作共享区的权利
锁的注意事项
1、cpu时间轮片
当线程T1执行写入0的操作的过程中,T1的cpu时间到了,那么就会让出cpu,之后给到T2,但是由于此时共享区还在被锁的状态,所以T2只好阻塞在锁上,等待锁的解锁,之后,过段时间,就会又让出cpu给到T3,T3此时也会阻塞在锁上,然后过段时间,再次让出cpu给T1,T1此时拿着锁,可以操作共享区,所有会继续上次中断的地方继续操作共享区
2、建议锁
假如说,当T3拿到cpu时间之后,并不阻塞在锁上,而是就要强制访问共享区,那么也是可以的,只不过会造成数据混乱而已,所以,互斥锁并不是一个强制的概念,而是一个建议,他的强制性体现在代码逻辑上,而不是底层系统的强制操作,如果T3的代码逻辑中使用了锁,那么T3就无法访问共享区,当然T3也可以不使用锁,直接访问,代码层面也是可以运行的
总结
使用锁来管理线程同步
问题产生
使用两个线程,同时向“公共输出”(共享区)写数据,没有加锁机制
效果:
打印完全随机,各个线程的一帧数据写入都会被其他线程打断、插入其他数据
主要函数
其中,trylock函数,表示会让当前线程尝试加锁,看看能不能加上,加不上了先去做自己的事,过段时间再来尝试,而不是像之前说的“阻塞在锁上”
而五个函数的下面是一个变量,这个变量就是互斥量,就是锁本身,他只有0、1两种取值
大概流程:
init、destory
对于init:
参数一:传入互斥锁的地址,
参数二:互斥锁的属性,如果想使用默认属性,那么就传NULL
其中,对于restrict关键字:
他是用来修饰指针的,
可以说,这个内存就认定了这个指针,注意与指针认定内存有区别(顶层const)
对于顶层const,是站在指针的角度,对于一个指针来说,他只存储某块内存的地址,但是该地址还可以被其他指针所存储和操作
而对于restrict,是站在内存的角度,对于一块内存的操作来说,他只认定那个指针,其他指针无法操作该内存
返回值:成功:0。失败:返回error number
lock、unlock
代码
1、创建互斥锁(全局变量)
2、在主线程创建子线程之前,要将锁初始化完毕
并在最后所有线程结束后销毁互斥锁:
3、在每个线程访问共享区的前后进行加锁和解锁:
子线程:
主线程:
效果:
每个线程的数据被完整的加入到了共享区
注意事项
假如说,我们把解锁的操作移到循环步的最后:
可以看到,主线程和子线程都使用加锁和解锁把原来的所有代码包裹起来,这样结构上确实很清晰
但是:
可以看到,最终要么只执行主线程,要么只执行子线程
原因:
由于线程在解锁之后立马就进入下一个循环,使得解锁之后该线程又拿到了锁,所以,就会一直保持一个线程,另一个线程无法拿到锁
所以:
我们的加锁和解锁操作,只在访问共享区的前后,立即执行,锁尽量只包含访问共享区的代码部分
补充:
可以将互斥锁的操作看成整数,
初始化时,值为1,表示当前锁可以被拿取并使用
之后,加锁时,–,那么值变为0,表示已经加锁,锁目前被占用,无法使用,其他线程就无法使用锁,无法访问共享区了
最后,解锁时,++,值变回1,表示再次可以使用