关于PV,信号量,同步,互斥的说明
互斥:
是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:
是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问,更强调进程间的协作,如同生产者消费者问题。
PV原语
PV操作是典型的同步机制之一。用一个信号量与一个消 息联系起来,当信 号量的值为0时,表示期 望的消息尚未产生;当信号 量的值非0时,表示期望的消息已经存在。用P V操作实现进程同步时,调用P操作测试消息是否到达,调用V操作发送消息,(P信号量减1,V信号量加1)
利用信号量和PV原语进行同步和互斥
利用信号量和PV操作实现进程互斥的一般模型是:
进程P1 进程P2 …… 进程Pn
…… …… ……
P(S); P(S); P(S);
临界区; 临界区; 临界区;
V(S); V(S); V(S);
…… …… …… ……
其中信号量S用于互斥,初值为1。
使用PV操作实现进程互斥时应该注意的是:
- 每个程序中用户实现互斥的P、V操作必须成对出现,先做P操作,进临界区,后做V操作,出临界区。若有多个分支,要认真检查其成对性。
- P、V操作应分别紧靠临界区的头尾部,临界区的代码应尽可能短,不能有死循环。
- 互斥信号量的初值一般为1。
利用信号量和PV操作实现进程同步:
PV操作是典型的同步机制之一。用一个信号量与一个消息联系起来,当信号量的值为0时,表示期望的消息尚未产生;当信号量的值非0时,表示期望的消息已经存在。用PV操作实现进程同步时,调用P操作测试消息是否到达,调用V操作发送消息。
使用PV操作实现进程同步时应该注意的是:
分析进程间的制约关系,确定信号量种类。在保持进程间有正确的同步关系情况下,哪个进程先执行,哪些进程后执行,彼此间通过什么资源(信号量)进行协调,从而明确要设置哪些信号量。
同一信号量的P、V操作要成对出现,但它们分别在不同的进程代码中。
参考:
http://blog.youkuaiyun.com/knight_coder/article/details/39237393
实战API
相关API说明:
pthread_cleanup_push和pthread_cleanup_pop,的使用
/* Install a cleanup handler: ROUTINE will be called with arguments ARG
when the thread is canceled or calls pthread_exit. ROUTINE will also
be called with arguments ARG when the matching pthread_cleanup_pop
is executed with non-zero EXECUTE argument.
pthread_cleanup_push and pthread_cleanup_pop are macros and must always
be used in matching pairs at the same nesting level of braces. */
# define pthread_cleanup_push(routine, arg) \
do { \
__pthread_cleanup_class __clframe (routine, arg)
/* Remove a cleanup handler installed by the matching pthread_cleanup_push.
If EXECUTE is non-zero, the handler function is called. */
# define pthread_cleanup_pop(execute) \
__clframe.__setdoit (execute); \
} while (0)
这一组函数主要用于保护锁操作区间,如果在return之前,发生异常,调用pthread_cleanup_push的回调函数,这个自定义的回调函数主要用来释放资源和锁等,避免资源浪费和死锁。如果正常返回,pthread_cleanup_pop则会调用来取消pthread_cleanup_push的回调函数。
线程接收到cancel后用一种方法来保证异常退出(也就是线程没达到终点)时可以做清理工作(主要是解锁方面),pthread_cleanup_push与pthread_cleanup_pop就是这样的。
sem_post和sem_wait的使用
typedef union
{
char __size[__SIZEOF_SEM_T];
long int __align;
} sem_t; //同步信号量
//=========semaphore.h as below==============================
/*
* int sem_init (sem_t *sem, int pshared, unsigned int value);
* sem指定的信号量进行初始化,设置好它的共享选项,并指定一个整数类型的初始值。
* pshared参数控制着信号量的类型。如果 pshared的值是0,就表示它是当前里程的局部信号量;否则,其它进程就能够共享这个信号量
*/
/* Initialize semaphore object SEM to VALUE. If PSHARED then share it
with other processes. */
extern int sem_init (sem_t *__sem, int __pshared, unsigned int __value)
__THROW;
//相当于信号的P操作,等待获取信号资源,信号资源减一
#define sem_wait(sem) (int(WAIT_OBJECT_0!=WaitForSingleObject(*sem,INFINITE))
//相当于信号的V操作,信号资源加一,释放信号资源
/* Wait for SEM being posted.
This function is a cancellation point and therefore not marked with
__THROW. */
extern int sem_wait (sem_t *__sem);
/* Post SEM. */
extern int sem_post (sem_t *__sem) __THROWNL;
pthread_mutex_lock和pthread_mutex_unlock的使用
pthread_mutex_lock 锁住一个临界区
pthread_mutex_lock 解锁一个临界区
pthread_cond_wait和pthread_cond_signal的使用
总结起来就是,X因为一个条件成立而处于等待状态;而线程Y让某个条件成立之后,发送唤醒X线程的信号。
//用完释放锁
/* Destroy condition variable COND. */
extern int pthread_cond_destroy (pthread_cond_t *__cond)
__THROW __nonnull ((1));
//线程X使条件成立,调用此函数来唤醒一个被这个condition控制而处于等待状态的线程Y,
/* Wake up one thread waiting for condition variable COND. */
extern int pthread_cond_signal (pthread_cond_t *__cond)
__THROWNL __nonnull ((1));
//唤醒所有被这个condition控制而处于等待状态的线程,使条件满足
/* Wake up all threads waiting for condition variables COND. */
extern int pthread_cond_broadcast (pthread_cond_t *__cond)
__THROWNL __nonnull ((1));
//一个等待条件成立,等待挂起线程
/* Wait for condition variable COND to be signaled or broadcast.
MUTEX is assumed to be locked before.
This function is a cancellation point and therefore not marked with
__THROW. */
extern int pthread_cond_wait (pthread_cond_t *__restrict __cond,
pthread_mutex_t *__restrict __mutex)
__nonnull ((1, 2));
操作实例:
这个案例是实现一个多对多的消费者生产模式
sem+mutex实现,使用pthread_cleanup_push实现接收非正常退出的处理
/*
* pc_v4.cpp
*
* Created on: Jul 13, 2017
* Author: buqing.wang
*/
/*
* 生产者-消费者 多对多模型
*/
#include<semaphore.h>
#include <iostream>
#include <queue>
#include<cstdlib>
#include<unistd.h>
#include<pthread.h>
#define SIZE 4
using namespace std;
int size = 8;
queue<int> products;
sem_t datasem; // 同步信号量,当没产品时阻止消费者消费,初始化为 0,因为起始状态,仓库没有产品,消费着不可操作
sem_t blanksem;//同步信号量,当满了时阻止生产者放产品;初始化为 生产者可操作的空间大小;为0表示满存不可操作
pthread_mutex_t mutex;//异步信号量 ,每次只能有一个线程访问仓库
int product_id = 0;
void interrupt_handle(void *param){
sem_post(&datasem);
pthread_mutex_unlock(&mutex);
cout<<"clean handle"<<endl;
}
void *product(void *arg)
{
while(1)
{
sleep(6);
pthread_cleanup_push(interrupt_handle,NULL);
sem_wait(&blanksem); //生产空间减小一个
pthread_mutex_lock(&mutex);
char* value=(char*)arg;
product_id++;
products.push(product_id);
cout<<value<<" MAKE "<<product_id<<" TOTAL :"<<products.size()<<endl;
/*
* sem_post函数的作用是给信号量的值加上一个“1”,
* 它是一个“原子操作”---即同时对同一个信号量做加“1”操作的两个线程是不会冲突的;
* 而同时对同一个文件进行读、加和写操作的两个程序就有可能会引起冲突。
* 信号量的值永远会正确地加一个
*
* 当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞,
* 选择机制同样是由线程的调度策略决定的。
*/
sem_post(&datasem);//产品增加一个
//cout<<"proce result :"<<endl;
pthread_mutex_unlock(&mutex);
pthread_cleanup_pop(0);
}
}
void *consume(void *arg)
{
while(1)
{
/**
* 函数sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,
* 表明公共资源经使用后减少。函数sem_trywait ( sem_t *sem )是函数sem_wait()的非阻塞版本,
* 它直接将信号量sem的值减一。
*
*
*/
sleep(1);
sem_wait(&datasem);
pthread_mutex_lock(&mutex);
char *value=(char*)arg;
if(products.size()<SIZE){
cout<<value<<" no product avilable!!"<<" TOTAL:"<<products.size()<<endl;
pthread_mutex_unlock(&mutex);
// cout<<"proce result :"<<endl;
sem_post(&datasem);
//cout<<"proce result B:"<<endl;
// sem_post(&blanksem);
continue;
}
// for(int i=0;i<=2;i++)
// {
// cout<<value<<" TAKE "<<products.front()<<endl;
// products.pop();
// }
cout<<value<<" TAKE "<<products.front()<<" TOTAL :"<<products.size()<<endl;
products.pop();
sem_post(&blanksem);
pthread_mutex_unlock(&mutex);
}
}
int main(int argc, char *argv[])
{
pthread_t a_pr;
pthread_t b_pr;
pthread_t i_cm;
pthread_t j_cm;
char * a_p ="A producer ";
char * b_p ="B producer ";
char * i_c ="I comsumer ";
char * j_c ="J comsumer ";
/*
* int sem_init (sem_t *sem, int pshared, unsigned int value);
* sem指定的信号量进行初始化,设置好它的共享选项,并指定一个整数类型的初始值。
* pshared参数控制着信号量的类型。如果 pshared的值是0,就表示它是当前里程的局部信号量;否则,其它进程就能够共享这个信号量
*/
sem_init(&datasem, 0, 0);
sem_init(&blanksem,0, 4);
pthread_create(&b_pr, NULL, product, (void*)b_p);
pthread_create(&a_pr, NULL, product, (void*)a_p);
pthread_create(&i_cm, NULL, consume, (void*)i_c);
pthread_create(&j_cm, NULL, consume, (void*)j_c);
pthread_join(b_pr, NULL);
pthread_join(a_pr, NULL);
pthread_join(i_cm, NULL);
pthread_join(j_cm, NULL);
sem_destroy(&datasem);//用来释放信号量sem
sem_destroy(&blanksem);
pthread_mutex_destroy(&mutex);
return 0;
}