目录
1.SYSTEM V信号量
(1) 创建或打开:semget
(2) 申请或释放:semop
(3) 设置信号量:semctl
2.POSIX信号量
(1) 初始化:sem_init
(2) 申请和释放:sem_wait
(3) 销毁:sem_destroy
(4) 获得信号量值:sem_getvalue:
3.进程同步与互斥
(1)进程互斥
(2)进程同步
4.线程同步与互斥
(1)互斥锁实现线程互斥
(2)条件变量实现线程同步
(3)信号量实现线程同步
5.综合实例
Linux系统上存在两套信号量标准:SYSTEM V信号量和POSIX信号量。
1.SYSTEM V信号量
头文件:
#inlclude <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
(1) 创建或打开:semget
int semget(key_t key, int nsems, int semflg);
成功返回信号量ID,失败返回-1
key:信号量集键值,常用ICP_PRIVATE由系统分配
nsems:信号量个数
semflg:常用IPC_CREAT代表没有新建信号量集则创建
m=semget(IPC_PRIVATE,1,0666|IPC_CREAT);
新建一个只有一个信号量的私有信号量集m
(2) 申请或释放:semop
int semop(int semid, struct sembuf *sops, unsigned nsops);
semid:信号量集ID
sops:操作定义的结构体
nsops:操作次数
semop(m,&sem_b,1);
struct sembuf sem_b;
sem_b.semnum=0;//要操作的信号量在信号量集中的索引,从0开始
sem_b.sem_op=-1;//负P正V
sem_b.sem_flg=SEM_UNDO;//进程意外结束时,进行UNDO
利用sem_b对m信号量集中索引为0的第一个信号做wait(P)操作
sem_b长度为1,意思是只做一个操作
进程意外结束时,将已做的wait进行UNDO
(3) 设置信号量:semctl
int semctl(int semid, int semnum, int cmd, union semun arg);
semid:信号量集ID
semnum:要操作的信号量在信号量集中的索引,从0开始
cmd:要执行的命令
SETVAL:设置一个信号量值
GETVAL:返回一个信号量值
arg:与cmd搭配使用
semctl(m,0,SETVAL,1);
设置信号量集m中第一个信号量的初值为1
2.POSIX信号量
(1) 初始化:sem_init
int sem_init(sem_t *sem, int pshared, unsigned int value);
sem:指向信号量的指针
pshared:0代表当前进程的所有线程共享,1代表进程间共享
value:信号量初值
(2) 申请和释放:sem_wait
int sem_wait(sem_t *sem);
减1为0则阻塞
int sem_post(sem_t *sem);
数值加1
(3) 销毁:sem_destroy
int sem_destroy(sem_t *sem);
sem:指向信号量的指针
(4) 获得信号量值:sem_getvalue:
int sem_getvalue(sem_t *sem, int *sval);
sem:指向信号量的指针
sval:获得的值保存在sval中
获得信号量的值,若阻塞则获得阻塞的进程/线程数,值保存到sval中
3.进程同步与互斥
(1)进程互斥
设有一个房间资源,一次只能一个进程使用,用信号量操作模拟3个进程互斥使用房间,输入q杀死进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
//5-13:进程互斥
int room = 0;
char ch;
int main(int argc,char argv[]) {
pid_t pid;
pid_t pids[2];
int i=0;
int j=0;
struct sembuf sem_b;
sem_b.sem_num=0;
sem_b.sem_flg=SEM_UNDO;
//创建控制进入房间的信号量
room=semget(IPC_PRIVATE,1,0666|IPC_CREAT);
//互斥信号量room赋初值为1
semctl(room,0,SETVAL,1);
for (i=0;i<=2;i++) {
pid=fork();//父亲循环几次,创建几个子进程。子进程由于while(1)不进入for循环
if (pid==0){
while(1){//子进程循环,父亲不进
printf("%d want to enter room--WAIT\n",getpid());
sem_b.sem_op = -1; //申请资源,操作数为1
semop(room,&sem_b,1); //此两句是信号量操作的申请wait
printf("%d is in room\n",getpid());
sleep(3);
sem_b.sem_op = 1; //释放资源,操作数为1
semop(room,&sem_b,1); //此两句是信号量操作的释放signal
printf("%d is out of room---SIGNAL OK\n",getpid());
}//while
}
else{
pids[i]=pid;//父亲
}
}//for
do{
ch=getchar();
if (ch == 'q')
for (i=0;i<=2;i++)
kill(pids[i],SIGTERM);
}while(ch != 'q');
} //main
父亲for循环3次,生成3个儿子,3个儿子进出房间
(2)进程同步
1个盘子能放1个水果,儿子读书累了就拿水果吃,没有水果就等待;父亲和母亲都不断给儿子放水果,盘子满了就不能放。用信号量操作模拟这3个进程的运行过程。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
//5-14:进程同步
int e; //empty of plate
int f; //fruit
int waitsem(int sem_id) {
//把信号量操作封装到函数中,提高代码可读性
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1;
sem_b.sem_flg = SEM_UNDO;
if(semop(sem_id,&sem_b,1) == -1){
fprintf(stderr, "P failed\n");
return 0; }//if
return 1;
}
int signalsem(int sem_id){
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1;
sem_b.sem_flg = SEM_UNDO;
if(semop(sem_id,&sem_b,1) == -1){
fprintf(stderr, "V failed\n");
return 0; }//if
return 1;
}
int main(int argc,char argv[]){
pid_t pid;
pid_t pids[2];
int i=0;
char ch;
e=semget(IPC_PRIVATE,1,0666|IPC_CREAT);
//创建e信号量用于控制对 盘子空资源 的使用
f=semget(IPC_PRIVATE,1,0666|IPC_CREAT);
//创建f信号量用于控制对 水果资源 的使用
semctl(e,0,SETVAL,1); //设置e初值为1
semctl(f,0,SETVAL,0); //设置f初值为0
//printf("%d\n",semctl(e,0,GETVAL,1));
//printf("%d\n",semctl(f,0,GETVAL,1));
for (i=0;i<3;i++) {
pid=fork();
if (pid>0)
pids[i]=pid;
if (pid==0&&i==0) //father
while(1){
waitsem(e);
sleep(rand()%5);
signalsem(f);
printf("DAD PUT FRUIT OK\n");
}//while
if (pid==0&&i==1) //mother
while(1){
waitsem(e);
sleep(rand()%3);
signalsem(f);
printf("MOM PUT FRUIT OK\n");
}//while
if (pid==0&&i==2) //son
while(1){
printf("Son want to eat\n");
waitsem(f);//孩子申请吃水果
printf("***Son is eating**************\n");
sleep(rand()%5);
signalsem(e);//孩子吃完,释放空盘子
printf("Son is reading\n");
}//while
}//for
do{
ch=getchar();
if(ch=='q')
for(i=0;i<3;i++)
kill(pids[i],SIGTERM);
}while(ch!='q');
}
有时候会观察到下图错误执行顺序,但代码没错,大概原因是父亲已经释放了f但是还没来得及输出语句,但是孩子有了f立刻就吃了并且输出。
如果想避免这种情况,可以把父亲的printf输出语句放到释放f前面
4.线程同步与互斥
#include <pthread.h>
(1)互斥锁实现线程互斥
定义与初始化:
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *restric attr);
mutex:指向互斥对象的内存区
attr:设置互斥对象属性,不需要则NULL
加/解锁:成功返回0,失败返回错误编号
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);互斥量被锁住时不阻塞,直接返回EBUSY表示共享资源忙碌
销毁:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
例:两个线程共享进程里的a、b变量,要求ThreadB负责输出ThreadA对a、b做运算后的结果,要求ThreadB在ThreadA运算过程中不能访问变量做输出。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
//5-15:线程互斥
int a = 100;
int b = 100;
pthread_mutex_t lock;
void *ThreadA(void *arg){
printf("A want a-50,b+50 \n");
pthread_mutex_lock(&lock); //加锁
a-= 50;
printf("A a-50 finish\n");
sleep(5); //执行到一半,用sleep释放cpu
b+= 50;
printf("A b+50 finish\n");
pthread_mutex_unlock(&lock); //解锁
}
void *ThreadB(void *arg){
pthread_mutex_lock(&lock);
sleep(3);
printf("B print: a+b should be 200 ,NOW a+b=%d\n", a + b);
pthread_mutex_unlock(&lock);
}
int main()
{
pthread_t tidA, tidB;
printf("MAIN init: a=%d,b=%d\n", a,b);
pthread_mutex_init(&lock, NULL);
//2个子线程,A执行的时候B不来打扰,B执行的时候也同样
pthread_create(&tidA, NULL,ThreadA,NULL);
pthread_create(&tidB, NULL,ThreadB,NULL);
pthread_join(tidA, NULL);//主线程等待A结束
pthread_join(tidB, NULL);//主线程等待B结束
pthread_mutex_destroy(&lock);
return 1;
}
代码中如果没有pthread_join,主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。
线程A,B沉睡的时间,沉睡的位置,以及是否使用信号量都会对结果造成不同的影响
所示代码:
A上锁,执行a-50,沉睡5s,B在这5s内不能操作a,b
A苏醒,完成b+50,解锁
B上锁,输出a+b结果,解锁
如果B注释掉锁,不需要和A抢信号,那么会输出以下结果
例:主进程产生两个线程,主进程等待线程共同对共享的进程的长整型变量x做加1操作,当加和到100000时停止工作,主进程在线程结束后输出加和后的结果。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
long x=0,y=0,z=0;
pthread_mutex_t lock;
void *thread_function1()
{
printf("thread_function1 is running \n");
while(x<1000000)
{
pthread_mutex_lock(&lock); //加锁
x++;
z++;
pthread_mutex_unlock(&lock); //解锁
}
}
void *thread_function2()
{
printf("thread_function2 is running \n");
while(x<1000000)
{
pthread_mutex_lock(&lock); //加锁
x++;
y++;
pthread_mutex_unlock(&lock); //解锁
}
}
int m=0;
int main(){
pthread_t thread1;
pthread_t thread2;
pthread_mutex_init(&lock, NULL);
pthread_create(&thread1,NULL,thread_function1,NULL);//创建新线程1,转到function1执行
pthread_create(&thread2,NULL,thread_function2,NULL);//创建新线程2,转到function2执行
sleep(2);
printf(" x= %d\n",x);
printf(" y= %d\n",y);
printf(" z = %d\n",z);
printf(" y+z= %d\n",y+z);
pthread_mutex_destroy(&lock);
exit(EXIT_FAILURE);
}
(2)条件变量实现线程同步
包括条件变量的创建、销毁、等待和释放等。通过设计条件变量,结合共享的互斥量实现线程间同步控制。
定义与初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *cond, pthread_cond_attr *cattr);
cond:指向已经分配好的内存区
cattr:设置属性,不用则NULL
阻塞/唤醒操作:
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *restrict mutex);
mutex:传给条件变量的互斥锁,阻塞时释放互斥锁,被唤醒时重新加锁
int pthread_cond_signal(pthread_cond_t *cond);
销毁:
int pthread_cond_destroy(pthread_cond_t *cond);
没有线程等待时才能注销条件变量,否则返回EBUSY
例:控制两个线程的合作
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
//例5-16:线程同步
int count=0;
pthread_cond_t cond; //条件变量
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; //互斥量
void *producer(void *mesgP){
while(1){
sleep(2);
pthread_mutex_lock(&mutex);
//关锁,保护count
count++;//生产产品
printf("%s produce ont product,count = %d\n",mesgP,count);
pthread_cond_signal(&cond);//唤醒阻塞的消费者
pthread_mutex_unlock(&mutex);
//开锁
sleep(rand()%3);
}//while
}
void *consumer(void *mesgC){
while(1){
pthread_mutex_lock(&mutex);
//给互斥量mutex加锁
while(count<=0){
//根据count决定消费者进程是否阻塞
printf("%s wait a condition...\n",mesgC);
pthread_cond_wait(&cond,&mutex);//阻塞消费者,cond-1,释放锁。如果未来被唤醒,会再次加上锁。
}
//消费产品
count--;
printf("%s one item,count = %d \n",mesgC,count);
pthread_mutex_unlock(&mutex);
sleep(rand()%10);
//设置消费者睡眠时间比生产者长,模拟消费速度慢的情况
}//whlie consumer
}
int main(void){
pthread_t threadP,threadC;
char *mesgC = "CONSUMER";
char *mesgP = "PRODUCER";
pthread_cond_init(&cond,NULL); //初始化条件变量cond
pthread_create(&threadC,NULL, consumer,(void*)mesgC);
pthread_create(&threadP,NULL, producer,(void*)mesgP);
//主线程暂停,等两个子线程执行完毕
pthread_join(threadC,NULL);
pthread_join(threadP,NULL);
pthread_cond_destroy(&cond);
//销毁条件变量cond
}
(3)信号量实现线程同步
例:主线程产生了两个线程,一个放入水果,一个取水果吃(POSIX信号量)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
int n;
sem_t product,empty;
void *eat(void *arg){
while (1) {
printf("**\tSon want to eat fruit\n");
sem_wait(&product);
sem_getvalue(&product,&n);
printf("**\tSon eated,there are %d fruits \n",n);
sem_post(&empty);
sleep(10);
}
}
void *putfruit(void *arg){
while (1) {
printf("Main want to put fruit\n");
sem_wait(&empty);
sleep(1);
sem_post(&product);
sem_getvalue(&product,&n);
printf("Main put fruit,fruit is %d \n",n);
}
}
int main(){
pthread_t threadP,threadC;
void *thread_result1,*thread_result2;
int semi,ret1,ret2;
semi = sem_init(&empty, 0, 5); //初始设置5个空资源,生产者可连续放入5次
if (semi == -1) {
printf("sem_init empty failed \n");
return -1;
}
semi = sem_init(&product, 0, 0); //初始设置0个产品数
if (semi == -1) {
printf("sem_init product failed \n");
return -1;
}
ret1 = pthread_create(&threadC, NULL,eat,NULL);
ret2 = pthread_create(&threadP, NULL,putfruit,NULL);
if (ret1 != 0|ret2!=0) {
printf("pthread_create failed \n");
return -1;
}
ret1 = pthread_join(threadC, &thread_result1);
ret2 = pthread_join(threadP, &thread_result2);
if (ret1!=0||ret2!=0){
perror("Thread join failed!\n");
exit(EXIT_FAILURE);
}
return 0;
}
一开始,孩子想吃水果,但是水果初值为0,孩子吃不到。直到主进程放进一个水果,水果数值为1,孩子才能吃水果,吃完以后水果数值再次归0。
5.综合实例
(1)经典的生产者消费者问题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#define SIZE 1024
int e,f,m,m2;
char ch;
int in=0,out=0;
int inok=0,outok=0;
int shmin,shmout;
int *shminaddr,*shmoutaddr;
int waitsem(int sem_id) {
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1;
sem_b.sem_flg = SEM_UNDO;
if(semop(sem_id,&sem_b,1) == -1){
fprintf(stderr, "P failed\n");
return 0;
}
return 1;
}
int signalsem(int sem_id){
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1;
sem_b.sem_flg = SEM_UNDO;
if(semop(sem_id,&sem_b,1) == -1){
fprintf(stderr, "V failed\n");
return 0;
}
return 1;
}
int main(int argc,char argv[]){
pid_t pid;
pid_t pids[20];
int i=0;
shmin=shmget(IPC_PRIVATE,SIZE,IPC_CREAT|0600);
shmout=shmget(IPC_PRIVATE,SIZE,IPC_CREAT|0600);
shminaddr=0;
shmoutaddr=0;
//创建4个信号量
e=semget(IPC_PRIVATE,1,0666|IPC_CREAT);
f=semget(IPC_PRIVATE,1,0666|IPC_CREAT);
m=semget(IPC_PRIVATE,1,0666|IPC_CREAT);
m2=semget(IPC_PRIVATE,1,0666|IPC_CREAT);
semctl(e,0,SETVAL,10); //设置e初值为10,表示当前有10个空,控制生产者只能放10个产品
semctl(f,0,SETVAL,0); //设置f初值为0,表示当前有0个产品,控制消费者可拿的产品数
semctl(m,0,SETVAL,1); //设置互斥信号量m初值为1,控制生产者对in的计数
semctl(m2,0,SETVAL,1); //设置互斥信号量m2初值为1,控制消费者对out的计数
for (i=0;i<20;i++) {
pid=fork();
if (pid>0)
pids[i]=pid;
if (pid==0 && i%2==0){ //生产者
shminaddr=(int *)shmat(shmin,NULL,0);//shmat共享内存
printf("Producer %d is begnning \n",i/2);
while(1){
waitsem(e);//占一个空
waitsem(m);//占in
//放入产品
inok=(* shminaddr)++;
printf("Producer put ok,in=%d,IN=%d \n",in+1,inok%10);
sleep(rand()%2);
signalsem(m);//释放in
signalsem(f);//释放一个产品
}
}
if (pid==0 && i%2==1) { //消费者
shmoutaddr=(int*)shmat(shmout,NULL,0);
printf("Consumer %d is begnning \n",i/2+i%2);
while(1){
waitsem(f);
waitsem(m2);
//取出产品
outok=(*shmoutaddr)++;
printf("Consumer out ok,out=%d,OUT=%d \n",out+1,outok%10);//?
sleep(rand()%6);
signalsem(m2);
signalsem(e);
}
}
}//for
do{
ch=getchar();
if(ch=='q')
for(i=0;i<20;i++)
kill(pids[i],SIGTERM);
}while(ch!='q');
}
20个子进程产生后,
每个都放入了一次产品后,
in++
或
out++
后的输出却都是
1。这是因为 fork 的克隆性操作,
每个新进程在自己内存中都有这两个in和out的副本
, 加 1
操作是加在了各自内存空间的副本变量上
,
并没有累加
。
通过内存映射,
给所有的子进程共享了某内存地址
(shminaddr,shmoutaddr
),
对共享变量的计数才有了累加(IN,OUT)。
为了防止修改的相互影响
,
对共享内存的操作多人间要互斥,
所以对
shminaddr ,shmoutaddr
变量的操作要写在互斥信号内
。
(2)黑白棋轮流下子问题
设两个围棋选手下棋,交替下子,sleep不同,第一个子的先后不同
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
//线程互斥
int turn=0;
int semi;
sem_t sBlack,sWhite;
pthread_mutex_t lock;
void *White(void *arg){
sleep(rand()%2);
pthread_mutex_lock(&lock); //加锁
if(turn==0){
turn=1;
semi=sem_init(&sWhite,0,1);//设置白棋信号为1,可以先下
if(semi==-1)
printf("White sem_init empty failed\n");
semi=sem_init(&sBlack,0,0);//设置黑棋信号为0,后下
if(semi==-1)
printf("Black sem_init empty failed\n");
printf("White fitst\n");
}
pthread_mutex_unlock(&lock);//开锁
while(1){
sem_wait(&sWhite);
printf("put a white piece \n");
sem_post(&sBlack);
sleep(2);
}
}
void *Black(void *arg){
sleep(rand()%2);
pthread_mutex_lock(&lock); //加锁
if(turn==0){
turn=1;
semi=sem_init(&sBlack,0,1);//设置黑棋信号为1,可以先下
if(semi==-1)
printf("Black sem_init empty failed\n");
semi=sem_init(&sWhite,0,0);//设置白棋信号为0,后下
if(semi==-1)
printf("White sem_init empty failed\n");
printf("Black fitst\n");
}
pthread_mutex_unlock(&lock);//开锁
while(1){
sem_wait(&sBlack);
printf("put a black piece \n");
sem_post(&sWhite);
sleep(2);
}
}
int main()
{
pthread_t tidA, tidB;
void *result1,*result2;
int res1,res2;
pthread_mutex_init(&lock, NULL);
//创建2个新线程
res1=pthread_create(&tidA,NULL,White,NULL);
res2=pthread_create(&tidB, NULL,Black,NULL);
if(res1!=0 | res2!=0){
printf("create failed\n");
}
res1=pthread_join(tidA, &result1);
res2=pthread_join(tidB, &result2);
if(res1!=0 | res2!=0){
printf("join failed\n");
exit(EXIT_FAILURE);
}
pthread_mutex_destroy(&lock);
return 0;
}
若黑方棋艺较高,开局让对方3个子,模拟两下棋进程之间的落子过程。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
//线程互斥
int turn=0;
int semi;
sem_t sBlack,sWhite;
pthread_mutex_t lock;
int count=0;
void *White(void *arg){
sleep(rand()%2);
pthread_mutex_lock(&lock); //加锁
if(turn==0){
turn=1;
semi=sem_init(&sWhite,0,1);//设置白棋信号为1,可以先下
if(semi==-1)
printf("White sem_init empty failed\n");
semi=sem_init(&sBlack,0,0);//设置黑棋信号为0,后下
if(semi==-1)
printf("Black sem_init empty failed\n");
printf("White fitst\n");
}
pthread_mutex_unlock(&lock);//开锁
while(1){
sem_wait(&sWhite);
count++;
printf("put a white piece \n");
if(count>=3){
sem_post(&sBlack);
}
sleep(2);
}
}
void *Black(void *arg){
while(count<3){
sleep(rand()%2);
sem_post(&sWhite);
}
printf("Black fitst\n");
while(1){
sem_wait(&sBlack);
printf("put a black piece \n");
sem_post(&sWhite);
sleep(2);
}
}
int main()
{
pthread_t tidA, tidB;
void *result1,*result2;
int res1,res2;
pthread_mutex_init(&lock, NULL);
//创建2个新线程
res1=pthread_create(&tidA,NULL,White,NULL);
res2=pthread_create(&tidB, NULL,Black,NULL);
if(res1!=0 | res2!=0){
printf("create failed\n");
}
res1=pthread_join(tidA, &result1);
res2=pthread_join(tidB, &result2);
if(res1!=0 | res2!=0){
printf("join failed\n");
exit(EXIT_FAILURE);
}
pthread_mutex_destroy(&lock);
return 0;
}
用count来计数,统计白棋下棋的次数,count=3表示白棋已经下了三步,此时黑棋可以下第一步。
(3)有个桶装纯净水水站,一个停车位用于停车装水,需装水的车依次进入停车位,装满后就离开,装水车规格都是一车装20桶水。两个装水工没工作时休息,一旦有车进入停车位就一起开始装水,车装满后就停止工作休息。
分析两个工人与车之间的同步关系,并用进程或线程同步模拟其运行过程。
提示:
- 车辆间对车位存在竞争的互斥关系;
- 装水工人和车之间存在相互唤醒的顺序关系;
- 如果从资源分析角度看,工人关系的是车上的桶空位,初始时工人需要等待车释放空位,每次车进入一次性释放20个桶空位。车进入车位后又需要等待,需工人释放装满信号后才能离开。
- 释放装满信号的不确定是哪个工人,需要结合计数变量的判断来决定释放操作由谁执行。
- 思考:调用 pthread_cond_wait() 之前,互斥对象处于什么状态?pthread_cond_wait() 调用返回之后,互斥量处于开锁还是关锁状态?(没有用这个)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
int i=0;
sem_t car,empty,go,m;
void *work(void *arg){
while(1){
sem_wait(&empty);//装入一箱
sleep(1);
sem_wait(&m);
i=i+1;
printf("worker%d input %d \n",arg,i);
if(i==20){
sem_post(&go);
i=0;
printf("车装满了,可以走了\n");
}
sem_post(&m);
}
}
void *getcar(void *arg){
int k=0;
while(1){
sem_wait(&car);//占一个车位
for(k=0;k<20;k++){
sem_post(&empty);//释放20个空
}
printf("%d 车到了,释放20个空\n",arg);
sem_wait(&go); //车能走吗?装满才能走
sem_post(&car); //走了释放停车位
sleep(2);
}
}
int main(){
int j=0;
pthread_t threadW1,threadW2,threadC;
void *thread_result1,*thread_result2;
sem_init(&car, 0, 1); //初始设置1个停车位
sem_init(&empty, 0, 0); //初始设置0个空
sem_init(&go, 0, 0); //初始设置0,车满的信号
sem_init(&m, 0, 1); //互斥信号量
pthread_create(&threadW1, NULL,work,(void *)1);
pthread_create(&threadW2, NULL,work,(void *)2);
for(j=1;j<=10;j++){//假设共10辆车
pthread_create(&threadC, NULL,getcar,(void *)j);
}
pthread_join(threadW1, &thread_result1);
pthread_join(threadW2, &thread_result1);
for(j=0;j<10;j++){
pthread_join(threadC, &thread_result2);
}
return 0;
}