操作系统实验——互斥与同步

2023-11-05

目录

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;      
} 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

操作系统实验——互斥与同步 的相关文章

随机推荐

  • unity 常用的设计模式

    一 单例模式 在我们的整个游戏生命周期当中 有很多对象从始至终有且只有一个 这个唯一的实例只需要生成一次 并且直到游戏结束才需要销毁 单例模式一般应用于管理器类 或者是一些需要持久化存在的对象 优点 写起来很方便 调用方便 缺点 容易形成依
  • 深度学习3D可视化工具——Zetane Engine

    神经网络在工作的时候 里面到底是什么样 为了能透视这个 AI黑箱 中的过程 加拿大蒙特利尔一家公司开发一个3D可视化工具Zetane Engine 只需要上传一个模型 Zetane Engine就可以巡视整个神经网络 并且还可以放大网络中的
  • React-Router实战:路由传参(正则表达式)

    首先我们先做个路由普通传参的例子 一 准备工作 1 目录结构 index js components App gt App js Home gt Home js About gt About js News gt News js 2 源码
  • Qt常用命令和pro参数

    Qt常用工具 命令行指令 位于 C Qt5 7 1 5 7 msvc2015 64 bin 命令 功能 assistant 帮助文档 designer 设计器 linguist 翻译工具 lupdate 提取翻译字符串和生成ts文件 lre
  • logback不输出至文件_Logback日志使用详解

    Logback是由log4j创始人设计的一个开源日志组件 概述 Logback建立于三个主要类之上 日志记录器 Logger 输出端 Appender 和日志格式化器 Layout 这三种组件协同工作 使开发者可以按照消息类型和级别来记录消
  • caffe 红绿灯识别

    coding utf 8 加载必要的库 import numpy as np import sys os 设置当前目录 caffe root home ubuntu caffe sys path insert 0 caffe root py
  • Report, 20150402, Formulas on Entropy, Part I

    Section 1 Papoulis s Formula Lemma 1 If the random variables y 1 ldots y n are the linear combination of random variable
  • Artifactory Maven 使用教程

    Maven 仓库使用 修改 Maven 配置文件 选择左侧 Artifacts 选择自己需要上传的 Maven 仓库 点击Set Me Up 在弹出的设置框中点击 点击下载生成的文件 将生成的文件放到此目录下 或者你自己的 Maven 目录
  • 线性回归模型

    线性回归是最简单的机器学习模型 也是最基础最重要的分析工具 易于实现 本文将将简单讲述线性回归 最小二乘法和梯度下降三种算法 目录 1 线性回归方程 OLS 2 最小二乘法 OLS 3 梯度下降 GD 3 1超参数 的选择 3 2局部最小值
  • Android使用OpenCV来实现bitmap独立设置每个圆角

    Android使用OpenCV来实现bitmap独立设置每个圆角 关于opencv集成请参考我的其他文章 这里方便起见已经封装成java方法供大家调用 代码如下 public static Bitmap drawCircleRadius i
  • 打乱1-100的个数字的顺序

    package test import java util Random public class Test2 public static void swap int a int i int j if a null a length 0 i
  • Weex页面的编写——Weex的学习之路(六)

    通过前几博客我们把weex的内置组件都学习完了 组件的单独使用想必都可以了 那么 这次我们来做weex实际页面的编写 见证一下 一套代码在Android Ios和H5上使用 在weex官网推荐我们使用Weex Studio作为编译器 其实这
  • Unity 3D作业二:离散仿真引擎基础

    前言 中山大学中山大学数据科学与计算机学院3D游戏课程学习记录博客 简答题 1 解释游戏对象 GameObjects 和资源 Assets 的区别与联系 游戏对象 出现在游戏场景中 充当游戏的组件 游戏对象不做任何事情 需要特殊属性才能成为
  • Xcode Executable Not Found

    问题 Xcode编译项目报Executable Not Found的错误 Details Details Executable Not Found Domain DVTMachOErrorDomain Code 5 Recovery Sug
  • Rust 删除排序数组中的重复项

    力扣https leetcode cn com problems remove duplicates from sorted array 参考代码和注释 fn main let mut v Vec
  • Linux下Elasticsearch离线安装

    先去下载离线安装包 我这里是7 10 0 Past Releases of Elastic Stack Software Elastic 上传到 usr local下 解压 tar zxvf elasticsearch 7 10 0 lin
  • 【MATLAB】MATLAB打开后,提示内部崩溃,直接闪退关闭——解决方法

    问题描述 在第一次安装MATLAB软件时 正常使用 过了一段时间后 突然发现在命令行可以正常使用 但运行编译文件里的程序便会报 MathWorks 崩溃的错误 提示MATLAB遇到了内部问题 需要关闭 结果MATLAB自己闪退结束 解决方法
  • MATLAB(6)GUI应用介绍

    目录 GUI编辑器 控件 属性 回调函数 MATLAB常见的控件 普通按钮 切换按钮 可编辑文本 字符获取 字符显示 复选框 单选按钮 弹出式菜单 滑动条 列表框 表 坐标区 附录 各文件共享数据 保存 获取 GUI编辑器 MATLAB的G
  • 【问题记录】05 Host key for [ip] has changed and you have requested strict checking.Host key verification

    1 报错信息如下 为主机ip WARNING REMOTE HOST IDENTIFICATION HAS CHANGED IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY Someo
  • 操作系统实验——互斥与同步

    目录 1 SYSTEM V信号量 1 创建或打开 semget 2 申请或释放 semop 3 设置信号量 semctl 2 POSIX信号量 1 初始化 sem init 2 申请和释放 sem wait 3 销毁 sem destroy