【操作系统实验】lab3 线程信号量同步与互斥

实验二、线程信号量同步与互斥

1、订票系统(临界区的管理)

下面这份代码是我们在学习线程同步之前可能会写出的代码。

由于线程之间是并发执行的,对于临界资源 t 同一时间可能会有两个线程去访问它,就会导致错误。比如如果在语句if(t>0)执行后,t--语句执行前发生了线程调度,另一个售票线程开始执行并且直到执行完毕才交出cpu的使用权,那在它看来剩余的票数仍然是2,那这两个售票线程都会将当前剩余的票数设置为1。两次售票线程执行后,剩余的票数不为0,即由线程执行的异步性导致了错误。

#include 
#include 
#include 

int ticketAmount = 2;

void* ticketAgent(void* arg){
	int t = ticketAmount;
	if(t > 0){
		printf("One ticket sold!\n");
		t--;
	}else{
		printf("Ticket sold out!\n");
	}
	ticketAmount = t;

	pthread_exit(0);
}

int main(){
	pthread_t ticketAgent_tid[2];

	for(int i = 0;i < 2;i++){
		pthread_create(ticketAgent_tid + i,NULL,ticketAgent,NULL);
	}

	for(int i = 0;i < 2;i++){
		pthread_join(ticketAgent_tid[i],NULL);
	}

	printf("The left ticket is %d\n",ticketAmount);
    
	return 0;
}

线程之间的并发执行导致这段程序运行的结果具有不确定性

【操作系统实验】lab3 线程信号量同步与互斥_第1张图片
【操作系统实验】lab3 线程信号量同步与互斥_第2张图片

多次运行后,这两种情况都会发生。

使用互斥锁工具对上述程序改进

使用互斥锁对临界区进行管理,使得同一时间只能由一个线程访问临界资源。

  • pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; 创建一个互斥锁

  • pthread_mutex_lock(&lock); 上锁

  • pthread_mutex_unlock(&lock); 开锁

#include 
#include 
#include 

int ticketAmount = 2;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; 

void* ticketAgent(void* arg){
	pthread_mutex_lock(&lock);
	int t = ticketAmount;
	if(t > 0){
		printf("One ticket sold!\n");
		t--;
	}else{
		printf("Ticket sold out!\n");
	}
	ticketAmount = t;
	pthread_mutex_unlock(&lock);
	pthread_exit(0);
}

int main(){
	pthread_t ticketAgent_tid[2];

	for(int i = 0;i < 2;i++){
		pthread_create(ticketAgent_tid + i,NULL,ticketAgent,NULL);
	}

	for(int i = 0;i < 2;i++){
		pthread_join(ticketAgent_tid[i],NULL);
	}

	printf("The left ticket is %d\n",ticketAmount);

	return 0;
}
2、生产者-消费者问题

问题描述:一组生产者进程和一组消费者进程共享一个初始为空、大小为 n 的缓冲区,缓冲区没满时,生产者才能把消息放入缓冲区,否则必须等待;只有缓冲区不空时,消费者才能从中取出消息,否则必须等待。由于缓冲区是临界资源,同一时刻只允许一个生产者放入消息,或一个消费者取出消息。

生产者与消费者之间是同步关系,使用信号量工具。

生产这与生产者,消费者与消费者之间是互斥关系,使用互斥锁工具。

信号量的初始值为 1 时,可以作为互斥锁使用。

  • 要使用信号量,请先包含头文件

  • sem_t 信号量的数据类型

  • int sem_init(sem_t *sem, int pshared, unsigned int val);
    该函数第一个参数为信号量指针,第二个参数为信号量类型(一般设置为0),第三个为信号量初始值。第二个参数pshared为0时,该进程内所有线程可用,不为0时不同进程间可用。

  • int sem_wait(sem_t *sem);
    该函数申请一个信号量,当前无可用信号量则等待,有可用信号量时占用一个信号量,对信号量的值减1。

  • int sem_post(sem_t *sem);
    该函数释放一个信号量,信号量的值加1。

  • int sem_destroy(sem_t *sem);
    该函数销毁信号量。

#include 
#include 
#include 
#include 

sem_t empty;
sem_t full;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; 

void* producerFunc(void* arg){
	for(int i = 0;i < 10;i++){
		sem_wait(&empty);
        
        pthread_mutex_lock(&lock);
        //get a message
		printf("The producer put one!\n");
        pthread_mutex_unlock(&lock);
        
		sem_post(&full);
	}
}

void* consumerFunc(void* arg){
	for(int i = 0;i < 10;i++){
		sem_wait(&full);
        
        pthread_mutex_lock(&lock);
        //put a meaaage
		printf("The consumer get one!\n");
        pthread_mutex_unlock(&lock);
        
		sem_post(&empty);
	}
}

int main(){
	pthread_t producer_tid;
	pthread_t consumer_tid;

	sem_init(&empty,0,1);
	sem_init(&full,0,0);

	pthread_create(&producer_tid,NULL,producerFunc,NULL);
	pthread_create(&consumer_tid,NULL,consumerFunc,NULL);

	pthread_join(producer_tid,NULL);
	pthread_join(consumer_tid,NULL);

	sem_destroy(&empty);
	sem_destroy(&full);
	
	return 0;
}
3、苹果橘子问题

问题描述:桌子上有一个盘子,每次只能向其中放入一个水果。爸爸专门向盘子中放苹果,妈妈专门向盘子中放橘子,儿子专等吃盘子中的橘子,女儿专等吃盘子中的苹果。只有盘子为空时,爸爸或妈妈才可向盘子中放一个水果;仅当盘子中有自己需要的水果时,儿子或女儿才可从中取出。

本质是一个较为复杂的生产者-消费者问题。可以理解为两个生产者和两个消费者被连接到大小为1的缓冲区上。

#include 
#include 
#include 
#include 

sem_t orange;
sem_t apple;
sem_t empty;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* fatherThread(void* arg){
	
	while(1){
		sem_wait(&empty);

		pthread_mutex_lock(&mutex);
		printf("father put an orange\n");
		sem_post(&orange);
		pthread_mutex_unlock(&mutex);

		sleep(1);
	}
}

void* motherThread(void* arg){
	while(1){
		sem_wait(&empty);

		pthread_mutex_lock(&mutex);
		printf("mother put an apple\n");
		sem_post(&apple);
		pthread_mutex_unlock(&mutex);

		sleep(1);
	}
}

void* sonThread(void* arg){
	while(1){
		sem_wait(&orange);

		pthread_mutex_lock(&mutex);
		printf("son get an orange\n");
		sem_post(&empty);
		pthread_mutex_unlock(&mutex);
	}
}

void* daughterThread(void* arg){
	while(1){
		sem_wait(&apple);

		pthread_mutex_lock(&mutex);
		printf("daughter get an apple\n");
		sem_post(&empty);
		pthread_mutex_unlock(&mutex);
	}
	sleep(100);
    sem_destroy(&empty);
    sem_destroy(&apple);
    sem_destroy(&orange);

}

int main(){
	pthread_t father;
	pthread_t mother;
	pthread_t son;
	pthread_t daughter;

	sem_init(&empty,0,3);
	sem_init(&orange,0,0);
	sem_init(&apple,0,0);

	pthread_create(&father,NULL,fatherThread,NULL);
	pthread_create(&mother,NULL,motherThread,NULL);
	pthread_create(&son,NULL,sonThread,NULL);
	pthread_create(&daughter,NULL,daughterThread,NULL);

	sleep(100);
    sem_destroy(&empty);
    sem_destroy(&apple);
    sem_destroy(&orange);

	return 0;
}
附录:相关头文件

头文件定义了三种类型的信号量(semaphore)和一些函数来创建、销毁、等待和释放信号量:

  1. sem_t:这是信号量的主要类型,表示一个信号量。sem_t 类型的变量在使用前需要初始化,可以通过 sem_init() 函数来完成。
  2. semaphore_attr_t:这是一个信号量属性的类型,它包含信号量的初始值和一些其他的属性。可以通过 sem_init() 函数的第三个参数来指定。
  3. pthread_mutexattr_t:这是一个互斥锁属性的类型,它包含互斥锁的属性。信号量的实现通常基于互斥锁。这个类型用于设置 sem_t 变量内部的互斥锁属性。

以下是一些常用的函数:

  • sem_init():初始化一个信号量。
  • sem_destroy():销毁一个信号量。
  • sem_wait():等待一个信号量变为非零值。
  • sem_trywait():尝试等待一个信号量变为非零值,如果不能立即获得,则返回一个错误码。
  • sem_post():将信号量的值增加 1。
  • sem_getvalue():获取信号量的当前值。

. pthread_mutexattr_t:这是一个互斥锁属性的类型,它包含互斥锁的属性。信号量的实现通常基于互斥锁。这个类型用于设置 sem_t 变量内部的互斥锁属性。

以下是一些常用的函数:

  • sem_init():初始化一个信号量。
  • sem_destroy():销毁一个信号量。
  • sem_wait():等待一个信号量变为非零值。
  • sem_trywait():尝试等待一个信号量变为非零值,如果不能立即获得,则返回一个错误码。
  • sem_post():将信号量的值增加 1。
  • sem_getvalue():获取信号量的当前值。

你可能感兴趣的:(linux,笔记,unix)