同步和互斥
互斥: 是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步: 是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。
条件变量机制
与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。
条件的检测是在互斥锁的保护下进行的。如果一个条件为假,个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,线程重新获得互斥锁,需要重新评价条件。
条件变量分为两部分:条件和变量.条件本身是由互斥量保护的.线程在改变条件状态前先要锁住互斥量,它利用线程间共享的全局变量进行同步的一种机制。
条件变量机制的存在意义
由于条件变量是用来等待一个条件发生的,在条件未发生之前,会阻塞使用该条件变量的线程,也就是说条件变量允许线程以无竞争的方式等待条件的发生。这样就不需要通过轮询的方式来判断条件大大节省了CPU时间。
条件变量的基本操作
1.初始化条件变量
一般由主线程来初始化一个条件变量,有动态和静态两种初始化方式具体实现:
静态:pthread cond t cond=PTHREAD COND INITIALIZER,
动态:pthread cond init 函数
函数 pthread_cond_init头文件 #include < pthread.h>
函数原型 int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
功能 用来初始化一个条件变量。他使用变量attr所指定的属性来初始化一个条件变量,如果
参数attr为空,那么它将使用默认的属性来设置所指定的条件变量。
参数 1.cond:条件变量;
2.attr:条件变量属性。
返回值 成功:返回0;
失败:返回错误号。
2.通知条件
条件已经满足,发送条件通知。
具体实现:pthread cond signal
pthread cond broadcast
函数 pthread_cond_destroy头文件 #include < pthread.h>
函数原型 int pthread_cond_destroy(pthread_cond_t *cond);
功能 用来销毁所指定的条件变量,同时将会释放所给它分配的资源。
参数 1.cond:条件变量;
返回值 成功:返回0;
失败:返回错误号。
3.等待条件成立等待条件变为真,
具体实现: pthread cond wait 函数
函数 pthread_cond_wait头文件 #include < pthread.h>
函数原型 int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
功能 条件变量等待。
参数 1.cond:条件变量;
2.mutex:互斥锁。
返回值 成功:返回0;
失败:返回错误号。
4.销毁条件变量
具体实现 pthread cond destory函数
函数 pthread_cond_signal/pthread_cond_broadcast头文件 #include < pthread.h>
函数原型 int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
功能 当调用pthread_cond_signal时,一个在相同条件变量上阻塞的线程将被解锁。如果同时
有多个线程阻塞,则由调度策略确定接收通知的线程。如果调用
pthread_cond_broadcast,则将通知阻塞在这个条件变量上的所有线程。一旦被唤醒,
线程仍然会要求互斥锁。如果当前没有线程等待通知,则上面两种调用实际上成为一个
空操作。如果参数cond指向非法地址,则返回EINVAL值。
参数 1.cond:条件变量;
返回值 成功:返回0;
失败:返回错误号。
信号量机制
信号量(semaphore)也被成为信号灯。是在多线程环境下用来保证关键代码段不被并发调用,在进入一个关键代码段之前每个线程必须要获取一个信号量,一旦关键代码段执行完成了,拥有信号量的线程必须释放信号量。另外想进入关键代码段的线程必须等待直到拥有信号量的线程释放了信号量。为了完成以上这个过程,就需要创建一个信号量,并且将获取信号量和释放信号量的操作分别放置在关键代码段的开始和结束处。
例:多个线程都想执行 读取一个文件内容的操作,但同一时刻该操作只能满足一个线程,为了保护读取文件内容的有序操作,就可以使用信号量来保护这段关键代码。
备注:信号量可以实现线程的同步和互斥,但大多数情况下用于线程间的同步.
信号量用于同步和互斥的基本用法
当信号量用于互斥的情况:几个进程(或线程)往往只设置一个信号量,且将信号量初值设为1。
当信号量用于同步的情况:几个进程(或线程)往往设置多个信号量,且将其中一个信号量初值设为1(通常设置为资源总数)。其他为0
PV原子操作的原理
前面的信号量也就是操作系统所用到的PV原子操作它广泛用于进程或线程之间的同步和互斥,信号量本身是一个非负整数计数器,而PV原子操作就是对这个整数计数器信号量(sem)的操作
P 操作:
一次P操作使信sem 减1,如果 sem>=0 进程或者线程具有公共资源的访问权;
V操作:
一次V操作使信sem 加1,如果 sem<0 进程或者线程就将阻塞,直到sem>=0为止
当一个进程或者线程需要访问关键代码段(临界区)时,它必须先执行操作原语,使信号量S减1,当完成对临界区访问后,执行V操作来释放占用的临界区,若信号量的初值为1,当一个进程或者线程执行P操作后S变成0,可以访问临界区,在该进程或者线程没有执行V操作之前,其他进程或线程也想访问临界区,也必须执行P操作,从而S就变成-1,因此第二个进程或线程就会被阻塞,直到第一个退出临界区,这时S变为0从而唤醒第二个进程或线程等待系统调度后再进入临界区,当第二个进程或线程访问完后,执行V操作,如果没有其他进程或线程申请进入,S变成初值1。也就是说:P(S)操作表示进程(或线程) 申请进入临界区,而V(S)操作表示进程(或线程)退出临界区
信号量相关函数
1.初始化信号量
函数 sem_init
头文件 #include < semaphore.h>
函数原型 int sem_init(sem_t *sem, int pshared, unsigned int value);
功能 初始化一个信号量,设置好它的共享选项,并指定一个整数类型的初始值。
参数 1. sem:指向信号量结构的指针;
- pshared:信号量是否可共享。只能为0,表明信号量一个进程的多个线程间共享。
Linux还没有实现进程间共享信号量。
- value: 信号量的初值
返回值 成功:返回0;
失败:返回-1,错误码存在 errno。
2.等待信号量
函数 sem_wait
头文件 #include < semaphore.h>
函数原型 int sem_wait(sem_t * sem);
功能 阻塞函数。从信号量的值减去一个"1",但它永远会先等待该信号量为一个非零值才开始
做减法。也就是说,如果你对一个值为2的信号量调用sem_wait(),线程将会继续执
行,结果是信号量的值将减到1。如果对一个值为0的信号量调用sem_wait(),这个函数
就会等待直到有其它线程增加了这个值使它不再是0为止。如果有两个线程都在
sem_wait()中等待同一个信号量变成非零值,那么当它被第三个线程增加一个"1"时,等
待线程中只有一个能够对信号量做减法并继续执行,另一个还将处于等待状态。
参数 1. sem:指向信号量结构的指针;
返回值 成功:返回0;
失败:返回-1,错误码存在 errno。
3.信号量增加
函数 sem_post
头文件 #include < semaphore.h>
函数原型 int sem_post(sem_t * sem);
功能 给信号量的值加上一个"1"。
参数 1.sem:指向信号量结构的一个指针。
返回值 成功:返回0;
失败:返回-1,错误原因存于error中。
4.释放信号量
函数 sem_destroy
头文件 #include < semaphore.h>
函数原型 int sem_destroy (sem_t *sem);
功能 释放信号量sem。
参数 1.sem:指向信号量结构的一个指针。
返回值 成功:返回0;
失败:返回-1。
5.获取信号量的值
函数 sem_getvalue
头文件 #include < semaphore.h>
函数原型 int sem_getvalue(sem_t * sem, int * semval);
功能 获取信号量的值。
参数 1.sem:指向信号量结构的一个指针。
2.semval:获取的信号量的值放到这个变量地址里,如果有一个或者多个进程或者线程
当前正在使用sem_wait等待信号量,semval可能会返回两种结果,要么,返回0,要么
返回一个负值,它的绝对值等于当前正在使用P操作的阻塞的进程或者线程数的总和。
注意:信号量的值可能在sem_getvalue返回时,已经被更改。
返回值 成功:返回0;
失败:返回-1。