Linux多线程之线程同步

  线程最大的特色就是资源的共享性,因此也就有了一个难点线程同步,实现线程同步的方法最经常使用的方法是:互斥锁,条件变量和信号量。接下来就让咱们来看下这几种同步的方法。
ide

1、互斥锁(Mutex)函数

  得到锁的线程能够完成“读-修改-写”的操做,而后释放锁给其它线程,没有得到锁的线程只能等待而不能访问共享数据,这样“读-修改-写”三步操做组成一个原子操做,要么都执行,要么都不执行,不会执行到中间被打断,也不会在其它处理器上并行作这个操做。post

一、锁的初始化和销毁(Mutex用pthread_mutex_t类型的变量表示)spa

wKioL1nmpo_Swv8XAABm7CySx6g673.png

  注意:若是Mutex变量是静态分配的(全局变量 或static变量),也能够用宏PTHREAD_MUTEX_INITIALIZER来初始化,至关于用pthread_mutex_init初始化而且attr参数为NULL。用函数初始化则是动态分配。
线程

二、加锁解锁blog

wKioL1nmp7GwBHHHAABTcKT1dPo031.png

  一个线程能够调用pthread_mutex_lock得到Mutex,若是这时另外一个线程已经调用pthread_mutex_lock得到了该Mutex,则当前线程须要挂起等待,直到另外一个线程调用
pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能得到该Mutex并继续执行。若是一个线程既想得到锁,又不想挂起等待,能够调用pthread_mutex_trylock,若是Mutex已经被另外一个线程得到,这个函数会失败返回EBUSY,而不会使线程挂起等待。
队列

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#define LOOP 5000
static int count = 0;
pthread_mutex_t mutex_lock = PTHREAD_MUTEX_INITIALIZER;
void *read_write(void *val)
{
	int _val = 0;
	int i = 0;
	for( ; i < LOOP; i++ ){
		pthread_mutex_lock(&mutex_lock);
		_val = count;
		printf("pthread id is :%x,count : %d\n",
				(unsigned long)pthread_self(),count);
		count = _val+1;
		pthread_mutex_unlock(&mutex_lock);
	}
}
int main()
{
	pthread_t tid1;
	pthread_t tid2;
	pthread_create(&tid1,NULL,read_write,NULL);//为了简单没有判断是否建立成功
	pthread_create(&tid2,NULL,read_write,NULL);
	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);
	printf("count final value is : %d\n",count);
	return 0;
}

若是没有加入互斥锁,运行结果不会是10000。进程

三、互斥锁的缺点内存

  死锁的状况:1)通常状况下,若是同一个线程前后两次调用lock,在第二次调用时,因为锁已经被占用,该线程会挂起等待别的线程释放锁,然而锁正是被本身占用着的,该线程又被挂起而没有机会释放锁,所以就永远处于挂起等待状态了。2)线程A得到了锁1,线程B得到了锁2,这时线程A调用lock试图得到锁2,结果是须要挂起等待线程B释放锁2,而这时线程B也调用lock试图得到锁1,结果是须要挂起等待线程A释放锁1,因而线程A和B都永远处于挂起状态了。资源

  解决办法:首先要尽可能避免同时得到多个锁。若是全部线程在须要多个锁时都按相同的前后顺序(常见的是按Mutex变量的地址顺序)得到锁,则不会出现死锁。

2、条件变量

  互斥锁不一样,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊状况发生为止。一般条件变量和互斥锁同时使用。条件变量分为两部分: 条件和变量。条件自己是由互斥量保护的。线程在改变条件状态前先要锁住互斥量。条件变量使咱们能够睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动做:一个线程等待"条件变量的条件成立"而挂起;另外一个线程使"条件成立"(给出条件成立信号)。条件的检测是在互斥锁的保护下进行的。若是一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。若是另外一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,从新得到互斥锁,从新评价条件。若是两进程共享可读写的内存,条件变量能够被用来实现这两进程间的线程同步。  

一、条件变量的初始化和销毁

wKiom1nms0jBDzejAABgwD25S88063.png

  条件变量初始化和互斥锁初始化相似,宏表示静态分配,函数表示动态分配,当函数的第二个参数为NULL则二者等价。

二、等待条件成立和激活条件变量

wKiom1nmtcWTdwg6AAByOUDxKcs887.png

wKioL1nmskCzx6YlAAA7Ziy_vK0848.png

  可见,一个条件变量老是和一个Mutex搭配使用的。 一个线程能够调用pthread_cond_wait在一个条件变量上阻塞等待,这个函数作如下三步操做:
1)释放Mutex
2)阻塞等待
3)当被唤醒时,从新得到Mutex并返回
  pthread_cond_timedwait函数还有一个额外的参数能够设定等待超时,若是到达了abstime所指定的时刻仍然没有别的线程来唤醒当前线程,就返回ETIMEDOUT。一个线程能够调用pthread_cond_signal唤醒在某个条件变量上等待的另外一个线程,也能够调用pthread_cond_broadcast唤醒在这个条件变量上等待的全部线程。

  下面写一个生产者和消费者的实现(链表):

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
typedef struct list{
	struct list *next;
	int val;
}product_list;
product_list *head = NULL;
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t need_product=PTHREAD_COND_INITIALIZER;
/*init list*/
void init_list(product_list *list)
{
	if(NULL != list){
		list->next = NULL;
		list->val = 0;
	}
}
/*consumer*/
void *consumer(void *_val)
{
	product_list *p = NULL;
	while(1){
		pthread_mutex_lock(&lock);
		while(NULL == head){
			pthread_cond_wait(&need_product,&lock);
		}
		p = head;
		head = head->next;
		p->next = NULL;
		pthread_mutex_unlock(&lock);
		printf("consum success,val is : %d\n",p->val);
		free(p);
		p = NULL;
	}
}
/*product*/
void *product(void *_val)
{
	while(1){
		sleep(rand()%2);
		product_list *p = (product_list*)malloc(sizeof(product_list));
		pthread_mutex_lock(&lock);
		init_list(p);
		p->val = rand()%1000;
		p->next = NULL;
		head = p;
		pthread_mutex_unlock(&lock);
        printf("product success,val : %d\n",p->val);
		pthread_cond_signal(&need_product);
	}
}
int main()
{
	pthread_t t_product;
	pthread_t t_consumer;
	pthread_create(&t_product,NULL,product,NULL);//no check
        pthread_create(&t_consumer,NULL,consumer,NULL);//no check

	pthread_join(t_product,NULL);//wait product thread
	pthread_join(t_consumer,NULL);//wait consumer thread
	return 0;
}


wKioL1nmvBqzoRzKAABvD0kOj9U458.png

3、信号量

  Mutex变量是非0即1的,可看做一种资源的可用数量,初始化时Mutex是1,表示有一个可用资源,加锁时得到该资源,将Mutex减到0,表示再也不有可用资源,解锁时释放该资源,将Mutex从新加到1,表示又有了一个可用资源。
  信号量(Semaphore)和Mutex相似,表示可用资源的数量,和Mutex不一样的是这个数量能够大于1。即,若是信号量描述的资源数目是1时,此时的信号量和互斥锁相同!POSIX semaphore库函数,这种信号量不只可用于同一进程的线程间同步,也可用于不一样进程间的同步。

一、信号量的建立和销毁

int sem_init(sem_t *sem , int pshared, unsigned int value);
int sem_destroy(sem_t *sem);

二、信号量的等待和释放

int sem_wait(sem_t *sem);//阻塞等待
int sem_trywait(sem_t *sem);//非阻塞等待

  调用sem_wait()能够得到资源(P操做),使semaphore的值减1,若是调用sem_wait()时semaphore的值已是0,则挂起等待。若是不但愿挂起等待,能够调用sem_trywait() 。调用sem_post() 能够释放资源(V操做),使semaphore 的值加1,同时唤醒挂起等待的线程。

  下面写一个生产者和消费者的实现(固定大小循环队列):

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>

#define SEM_PRO 10
#define SEM_COM 0
/*定义信号量*/
sem_t sem_product;
sem_t sem_consume;
int bank[SEM_PRO];
/*消费者线程执行函数*/
void *consumer(void *_val)
{
	int c = 0;
	while(1){
		sem_wait(&sem_consume);
		int _consume = bank[c];
		printf("consumer done...,val : %d\n",_consume);
		sem_post(&sem_product);
		c = (c+1)%SEM_PRO;
		sleep(rand()%5);
	}
}
/*生产者线程执行函数*/
void *producter(void *_val)
{
	int p = 0;
	while(1){
		sem_wait(&sem_product);
		int _product = rand()%100;
		bank[p] = _product;
		printf("product done... val is : %d\n",_product);
		sem_post(&sem_consume);
		p = (p+1)%SEM_PRO;
		sleep(rand()%3);
	}
}
/*建立生产者消费者线程*/
void run_product_consume()
{
	pthread_t tid_consumer;
	pthread_t tid_producter;

	pthread_create(&tid_consumer,NULL,consumer,NULL);
	pthread_create(&tid_producter,NULL,producter,NULL);
    
	pthread_join(tid_consumer,NULL);
	pthread_join(tid_producter,NULL);
}
/*销毁信号量*/
void destroy_all_sem()
{
	printf("process done...\n");
	sem_destroy(&sem_product);
	sem_destroy(&sem_consume);
	exit(0);
}
/*初始化信号量*/
void init_all_sem()
{
	int bank[SEM_PRO];
	int num = sizeof(bank)/sizeof(bank[0]);
	int i = 0;
	for(; i < num; i++ ){
		bank[i] = 0;
	}
	sem_init(&sem_product,0,SEM_PRO);
	sem_init(&sem_consume,0,SEM_COM);
}

int main()
{
	init_all_sem();
	run_product_consume();
//	destroy_all_sem();//避免销毁信号量
	return 0;
}


wKioL1nmyyTDE2GvAABrfUyaGA8668.png

相关文章
相关标签/搜索