下面这份代码是我们在学习线程同步之前可能会写出的代码。
由于线程之间是并发执行的,对于临界资源 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;
}
线程之间的并发执行导致这段程序运行的结果具有不确定性:
多次运行后,这两种情况都会发生。
使用互斥锁工具对上述程序改进
使用互斥锁对临界区进行管理,使得同一时间只能由一个线程访问临界资源。
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;
}
问题描述:一组生产者进程和一组消费者进程共享一个初始为空、大小为 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;
}
问题描述:桌子上有一个盘子,每次只能向其中放入一个水果。爸爸专门向盘子中放苹果,妈妈专门向盘子中放橘子,儿子专等吃盘子中的橘子,女儿专等吃盘子中的苹果。只有盘子为空时,爸爸或妈妈才可向盘子中放一个水果;仅当盘子中有自己需要的水果时,儿子或女儿才可从中取出。
本质是一个较为复杂的生产者-消费者问题。可以理解为两个生产者和两个消费者被连接到大小为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)和一些函数来创建、销毁、等待和释放信号量:
sem_t
:这是信号量的主要类型,表示一个信号量。sem_t
类型的变量在使用前需要初始化,可以通过 sem_init()
函数来完成。semaphore_attr_t
:这是一个信号量属性的类型,它包含信号量的初始值和一些其他的属性。可以通过 sem_init()
函数的第三个参数来指定。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()
:获取信号量的当前值。