pthread实现控制多线程中的一个最简单的实现方式就是使用信号量和互斥量,这两个侧重点和使用方式略有不同,但是有关信号量和互斥量之间的操作全部都是原子操作。
信号量
在谈论信号量的具体实现之前,先讨论一个最经典的问题,生产者消费者问题,这在操作系统课程上经常被提及的一个经典的例子。生产者生产出一个产品,消费者消耗一个产品,如果目前没有产品被生产出,那么消费者要等待生产者生产出相应的产品。要实现这样一个最简单的问题,我们需要有一个全局的数字,去标记当前环境中的产品数量,生产者生产一个+1,消费者消耗一个-1,为0时消费者阻塞等待,同时我们需要保证任何关于这个数值的操作是原子的。那么这个操作我们可以用信号量来实现。
信号量sem_t包含在头文件pthread.h中,有关sem_t主要的操作函数有sem_init、sem_wait、sem_post、sem_destroy,下面依次介绍所有函数的具体的操作。
int sem_init(sem_t* sem,int pshared,unsigned int value)
该方法创建一个新的信号量
第一个参数sem_t*类型的指针,指向唯一确定的信号量
第二个参数值为0的情况下表示信号量的值不能在进程之间共享
第三个参数是信号量的初始值,一般我们设置为0
int sem_wait(sem_t* sem)
int sem_post(sem_t* sem)
这两个函数是信号量的关键函数,主要wait方法对信号量-1,如果信号量值为0,则会一直阻塞,post方法对信号量执行+1操作
int sem_destroy(sem_t* sem);
该方法销毁一个信号量
这样,我们就可以用信号量来实现我们之前所说的关于消费者生产者程序,我们只需要在消费者线程中循环执行wait方法,在生产者线程中执行post方法即可,下面这段代码实现了两个线程,主线程接受用户的输入,子线程将用户的输入printf出来
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
#define SIZE 60
void* show_message(void* arg);
sem_t bin_sem;
char message[SIZE];
int main(){
int res;
pthread_t consumer;
void* thread_result;
res=sem_init(&bin_sem,0,0);
if(res!=0){
printf("something wrong with create sem \n");
exit(EXIT_FAILURE);
}
res=pthread_create(&consumer,NULL,show_message,0);
if(res!=0){
printf("something wrong with create the child thread\n");
exit(EXIT_FAILURE);
}
printf("please imput some words , end with the words 'end' \n");
while(strncmp("end",message,3)!=0){
fgets(message,SIZE,stdin);
sem_post(&bin_sem);
}
res=pthread_join(consumer,&thread_result);
if(res!=0){
printf("something wrong with join thread\n");
exit(EXIT_FAILURE);
}
printf("waiting for the end...");
sem_destroy(&bin_sem);
exit(EXIT_SUCCESS);
}
void* show_message(void* arg){
sem_wait(&bin_sem);
while(strncmp("end",message,3)!=0){
printf("your input is %s\n",message);
sem_wait(&bin_sem);
}
pthread_exit(NULL);
}
互斥量
互斥量的作用是他会锁住某个对象,同一时刻只有一个线程可以访问他,这样的话,我们可以对关键代码用互斥量保护起来,比如操作共享对象的操作,我们需要用互斥量保护起来。
互斥量的主要函数主要有以下几个
int pthread_mutex_init(pthread_mutext_t* mutex,const pthread_mutexattr_t* attr)
该方法实现的功能是初始化一个mutex对象,第一个参数是mutex对象指针,第二个参数是相关属性
int pthread_mutex_lock(pthread_mutext_t* mutex)
int pthread_mutex_unlock(pthread_mutex_t* mutex)
这两个方法实现的对互斥量进行加锁或者解锁,如果某个线程想要执行lock,而同时其他的一个线程已经lock,而且没释放,那么该线程便会阻塞,直到获得锁,进而执行后续的代码。
因此上面的代码片,我们也可以用互斥量实现(虽然上面是一个典型的生产者、消费者问题,用mutex实现稍显臃肿,mutex更适合去模拟JAVA的synchronize关键字),我们只需要对所有有可能操作message实现的地方加上互斥量去保护即可。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
void* thread_function(void* arg);
pthread_mutex_t mutex;
#define SIZE 50
char message[SIZE];
int time_to_exit=0;
int main(){
int res;
pthread_t a_thread;
void* thread_result;
res=pthread_mutex_init(&mutex,NULL);
if(res!=0){
printf("something wrong with creating mutex\n");
exit(EXIT_FAILURE);
}
res=pthread_create(&a_thread,NULL,thread_function,NULL);
if(res!=0){
printf("something wrong with creating new thread\n");
exit(EXIT_FAILURE);
}
pthread_mutex_lock(&mutex);
printf("exter a words,end with the words 'end' \n");
while(!time_to_exit){
fgets(message,SIZE,stdin);
pthread_mutex_unlock(&mutex);
while(1){
pthread_mutex_lock(&mutex);
if(message[0]!='\0'){
pthread_mutex_unlock(&mutex);
sleep(1);
}
else{
break;
}
}
}
pthread_mutex_unlock(&mutex);
printf("waiting for child thred end...\n");
res=pthread_join(a_thread,&thread_result);
if(res!=0){
printf("something wrong with creating join\n");
exit(EXIT_FAILURE);
}
printf("thread join \n");
pthread_mutex_destroy(&mutex);
exit(EXIT_SUCCESS);
}
void* thread_function(void* arg){
sleep(1);
pthread_mutex_lock(&mutex);
while(strncmp(message,"end",3)!=0){
printf("your input is %s\n",message);
message[0]='\0';
pthread_mutex_unlock(&mutex);
sleep(1);
pthread_mutex_lock(&mutex);
while(message[0]=='\0'){
pthread_mutex_unlock(&mutex);
sleep(1);
pthread_mutex_lock(&mutex);
}
}
time_to_exit=1;
message[0]='\0';
pthread_mutex_unlock(&mutex);
pthread_exit(0);
}
其他
其实这两段代码都存在一个问题,如果生产者生产速度远大于消费者消费速度,那么可能存在部分生产内容被丢弃,因为我们严格控制了消费者,而没有控制生产者,由于我们两段代码都是基于共享内存的。所以如果,我们把多线程的实现改为基于消息通知(go语言就是这样实现的),就可以一定程度上解决这种问题。