在进行多线程编程的时候
因为资源竞争的原因
多个线程必须进行精确的同步
我们可以使用互斥 信号量或条件变量
对线程进行同步
我们首先来看这样的编程任务
假设我们存在一个任务队列
多个并发线程会同时处理这些任务
每一个线程在完成
它自己的当前那个任务之后
会检查这个任务队列里边
有没有新的任务
如果有 它就会把这个任务
从任务队列中取出来
然后去执行这个任务
这是我们这个线程要做的事
这个任务被某个线程
从任务队列中取出来了
那么这个任务
一定要从线程队列里面把它清除掉
现在我们假设
有两个线程碰巧都完成了各自的任务
任务队列里边呢
只有一个新任务还没有处理
可能发生的情况
第一个 线程发现这个任务队列里非空
然后它就要接收这个新的任务
但是它没有来得及完成全部的设置
这个时候操作系统碰巧中断了这个线程
启动了另外一个线程
另外一个线程也发现这个任务队列非空
然后它同样准备接受这个新任务
发现没办法设置了 处理不了了
最坏的情况
就是第一个线程已经从任务队列里边
把这个任务摘取出来了
但是还没有把那个任务队列清空
第二个线程
想去那个任务队列里边取数据
就会导致段错误 系统崩溃
这就是资源竞争条件下
多个线程运行的时候
要特别注意的问题
具体的代码是这个样子
假设我有一个列表
它是一个双向链表
使用一个标准模板库的
std::list这个类模板
作业嘛
我们随便定义一个结构体Job
链表里存的是指向这个job的指针
链表的名字叫job_queue
我们的任务队列
线程函数是DequeueJob()
把这个job从这个任务队列里边摘出来
出对 就这个意思
当这个任务队列非空的时候
我们就取这个任务队列的队首元
用job_queue.front()取到它的队首元
就是队列的顶头那个元素
叫队首 队首元
取到它 然后我们job_queue.pop_front()
弹出它
从这个任务队列里边
把这个队首元给删除掉
我们处理这个作业
这个任务处理完
我们销毁这个任务 Over
这就是DequeueJob()
但是当这个任务队列里边
只有一个任务
两个线程去竞争访问的时候
这段程序代码就会出问题
单线程的程序 这一切都很正常
但是多线程编程的时候
就需要特别注意 这就有问题
我们怎么解决它
第一种解决方案——互斥
什么叫互斥
互斥就是相互独占锁
它和二元信号量是非常非常类似的
一次只有一个线程可以锁定
一个数据对象并且访问它
其它线程必须等待
你等我用完了你才能用
这就叫相互独占
你可以独占 我也可以独占
但是不管怎么样
在任何时候只能有一个线程访问它
其它线程必须等待
这个就叫互斥
我们可以使用函数pthread_mutex_init()
这个函数 来初始化一个互斥对象
那个互斥对象的那个型
就叫pthread_mutex_t 初始化它
可以使用一个特定的互斥属性初始化它
有一个宏叫PTHREAD_MUTEX_INITIALIZER
可以完成互斥体的初始化
我们可以使用pthread_mutex_destroy()
销毁这个互斥
可以使用pthread_mutex_lock()
去锁定这个互斥
获取这个互斥的访问
pthread_mutex_lock()这个函数去锁定它
如果没有办法锁定的话
当前的线程就会被阻塞
一直到它能够成功地锁定
trylock 就tpthread_mutex_trylock()这个函数呢
它会尝试着去锁定它
当它锁定不成功的时候
这个函数就会被返回
它就不会阻塞这个当前线程
你用完了 你不就不用了嘛
那么这个锁你就得释放
你就得把这个锁给解锁
刚才是加锁 现在你就得解锁
解锁的话就使用
tpthread_mutex_unlock()解锁它
这几个函数都比较简单
但使用的时候都是需要特别注意的
一般来讲
你使用互斥的时候有一个固定的流程
首先你要定义pthread_mutex_t
这个型的一个对象
然后尝试着去锁定或者锁定这个互斥
获得访问权以后就做你特定的处理
处理完以后要解锁
设定这个互斥的时候
可以指定互斥体的属性
一般情况下边
我们可以将这个互斥属性设置为
进程共享的或进程私有的
什么意思呢
就是说这个互斥可以跨进程的共享
或者只能在本进程内共享
也就是本进程内所有的线程都共享它
但是不是本进程的线程
是不可以访问它的
就这个意思
大部分的时候
我们使用都是PTHREAD_PROCESS_PRIVATE
就是进程私有的
本进程内共享 进程外不共享
有的操作系统
就不支持进程间共享互斥
就只能是进程内共享
大部分操作系统都可以支持
但是很多时候
因为我们做的是多线程编程
往往是一个进程的很多个线程
所以进程内共享是最常见的属性
互斥锁 它实际上是有些特定的型的
在设定互斥属性的时候
我们可以设定它的type
也就是它的型
有几种典型的互斥锁
一个是PTHREAD_MUTEX_NORMAL
它是一种普通的锁 普通互斥
第二种就PTHREAD_MUTEX_ERRORCHECK
它是一种检错锁
它是做错误检查的
这个线程对已经被其它线程锁定的
互斥加锁的时候
它就会返回一个错误
它就能够检测出这个错误来
第三个就PTHREAD_MUTEX_RECURSIVE
它是一个递归锁
就允许一个线程对一个互斥
进行多次的加锁或解锁的动作
你加锁几次
当然你要解锁几次 是对应的
第四个就PTHREAD_MUTEX_DEFAULT
它就是一个缺省的一个锁
实现是上面三种中的某一个
它并不是单独的一个选择
缺省的时候
大部分系统使用都是第一个
PTHREAD_MUTEX_NORMAL
它是一个普通锁
我们用的最多的就是它
但是它会有一个主要的问题
当这样一个锁被某一个线程锁定以后
其它请求加锁的线程都会被阻塞
它将等待 特别注意这一点
这是一个非常简单的实现
它就有可能在某些特殊的情况下边
会导致死锁
这是需要特别注意的
当解锁被其它线程锁定
或者已经解锁的互斥的时候
这个结果可能是无定义的
会导致不可预计的结果
所以要特别注意这一点
有一系列的可以设定 获取 修改
互斥属性的函数
像pthread_mutexattr_init()
这是初始化互斥体属性的
pthread_mutexattr_destroy()
这是销毁互斥属性对象的
还有获取pshared属性
设置它的pshared属性
获取它的type属性
设置它的type属性的
等等等等 这些函数
在设置互斥属性的时候你都可以用