Linux编程三种常用线程同步

时间:2021-02-12 11:00:53

本文介绍三种常用的线程同步方法,互斥量、条件变量、信号量

共同需要的头文件为#include<pthread.h>

互斥量强调资源访问互斥

条件变量是通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足

信号量强调线程间同步

一、互斥量(mutex

互斥量在同一时间只有一个线程能访问,任何其他试图再次对互斥量加锁的线程都会被阻塞直到当前线程释放该互斥锁。

Linux下,线程的互斥量数据类型是pthread_mutex_t。在使用前,要对它进行初始化。

1、初始化

静态分配:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
动态分配:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr_t *mutexattr);

2、加锁

对共享资源的访问,要对互斥量进行加锁,如果互斥量已经上了锁,调用线程会阻塞,直到互斥量被解锁。
int pthread_mutex_lock(pthread_mutex *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);

3、解锁

在完成了对共享资源的访问后,要对互斥量进行解锁。
int pthread_mutex_unlock(pthread_mutex_t *mutex);

4、销毁锁

如果使用动态分配互斥量,在释放内存前,需要进行销毁以释放资源。
int pthread_mutex_destroy(pthread_mutex *mutex);

给个例子,创建三个线程,用于给sum自增,并打印值,第四个线程条用alarm定时器,五秒后程序结束。

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<signal.h>
void sig_alarm();
pthread_t mutex;
int sum=0;
void *pthread1(void *arg)
{
	while(1)
	{
		pthread_mutex_lock(&mutex);
		sum++;
		printf("pthread1 sum=%d\n",sum);
		pthread_mutex_unlock(&mutex);
		sleep(1);
	}
}
void *pthread2(void *arg)
{
	while(1)
	{
		pthread_mutex_lock(&mutex);
		sum++;
		printf("pthread2 sum=%d\n",sum);
		pthread_mutex_unlock(&mutex);
		sleep(1);
	}
}
void *pthread3(void *arg)
{
	while(1)
	{
		pthread_mutex_lock(&mutex);
		sum++;
		printf("pthread3 sum=%d\n",sum);
		pthread_mutex_unlock(&mutex);
		sleep(1);
	}
}
void *pthread4(void *arg)
{
	signal(SIGALRM,sig_alarm);
	alarm(5);	
}
void sig_alarm()
{
	exit(0);
}
int main()
{
	pthread_t pth1,pth2,pth3,pth4;
	pthread_mutex_init(&mutex,NULL);
	pthread_create(&pth1,NULL,pthread1,NULL);
	pthread_create(&pth2,NULL,pthread2,NULL);
	pthread_create(&pth3,NULL,pthread3,NULL);
	pthread_create(&pth4,NULL,pthread4,NULL);
	pthread_join(pth1,NULL);
	pthread_join(pth2,NULL);
	pthread_join(pth3,NULL);
	pthread_join(pth4,NULL);
	return 0;
}

Linux编程三种常用线程同步

二、条件变量(cond

    条件本身是由互斥量保护的,线程在改变条件转态之前必须首先锁住互斥量,条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。

线程的条件变量数据类型是pthread_cond_t。在使用前,要对它进行初始化。

1、初始化条件变量

静态态初始化,pthread_cond_t cond = PTHREAD_COND_INITIALIER;
动态初始化,int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

2、等待条件成立

释放锁,同时阻塞等待条件变量为真才行。timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait),调用wait后必须再检测条件
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);

3、激活条件变量

通知线程条件已经满足
int pthread_cond_signal(pthread_cond_t *cond);至少能唤醒一个等待该条件的线程
int pthread_cond_broadcast(pthread_cond_t *cond); 能唤醒所有等待该条件的线程

4、清除条件变量

无线程等待,否则返回EBUSY

int pthread_cond_destroy(pthread_cond_t *cond);

    给个例子,创建三个线程,第一和第二个线程用于sum自增,如果sum值大于50就调用signal函数激活条件变量,第三个线程在条件不满足时被wait阻塞起来,调用signal函数后,wait函数会直接获得mutex锁,进行下一步,给sum清0

#include<stdio.h>
#include<pthread.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
int sum=0;
void *pthread1(void *arg)
{
	while(1)
	{
		
		if(sum<50)
		{
			pthread_mutex_lock(&mutex);
			sum+=10;
			printf("pthread1 sum=%d\n",sum);
			pthread_mutex_unlock(&mutex);
		}
		if(sum>=50)
		{	
			pthread_cond_signal(&cond);
		}
		sleep(1);
	}
}
void *pthread2(void *arg)
{
	while(1)
	{
		if(sum<50)
		{
			pthread_mutex_lock(&mutex);
			sum+=5;
			printf("pthread2 sum=%d\n",sum);
			pthread_mutex_unlock(&mutex);
		}
		if(sum>=50)
		{	
			pthread_cond_signal(&cond);
		}
		sleep(1);
	}
}
void *pthread3(void *arg)
{
	while(1)
	{
		pthread_mutex_lock(&mutex);
		while(sum<50)
		{
			pthread_cond_wait(&cond,&mutex);
		}
		sum=0;
		printf("pthread3 sum=%d\n",sum);
		pthread_mutex_unlock(&mutex);
	}
}
int main()
{
	pthread_t pth1,pth2,pth3;
	pthread_mutex_init(&mutex,NULL);
	pthread_cond_init(&cond,NULL);
	pthread_create(&pth1,NULL,pthread1,NULL);
	pthread_create(&pth2,NULL,pthread2,NULL);
	pthread_create(&pth3,NULL,pthread3,NULL);
	pthread_join(pth1,NULL);
	pthread_join(pth2,NULL);
	pthread_join(pth3,NULL);
	return 0;
}

Linux编程三种常用线程同步

三、信号量(sem

信号量是一个特殊变量,程序对其访问都是原子操作。

线程的信号量数据类型是sem_t。在使用前,要对它进行初始化。

1、信号量初始化
int sem_init (sem_t *sem , int pshared, unsigned int value);
这是对由 sem 指定的信号量进行初始化,设置好它的共享选项 (linux  只支持为 0 ,即表示它是当前进程的局部信号量 ) ,然后给它一个初始值 VALUE
2、等待信号量

给信号量减1,然后等待直到信号量的值大于0
int sem_wait(sem_t *sem);

3、释放信号量

信号量值加1。并通知其他等待线程。
int sem_post(sem_t *sem);

4、销毁信号量

我们用完信号量后都它进行清理。归还占有的一切资源。

int sem_destroy(sem_t *sem);

    给个例子,设置两个信号量,创建两个线程,进行对s1和s2的信号量进行PV操作,第三个线程为调用alarm定时器,让程序一秒后退出

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<signal.h>
#include<unistd.h>
#include<semaphore.h>
void sig_alarm();
sem_t s1,s2;
void *pthread1(void *arg)
{
	while(1)
	{
		sem_wait(&s1);
		printf("this is pthread1\n");
		sem_post(&s2);
	}
}
void *pthread2(void *arg)
{
	while(1)
	{
		sem_wait(&s2);
		printf("this is pthread2\n");
		sem_post(&s1);
	}
}
void *pthread3(void *arg)
{
	signal(SIGALRM,sig_alarm);
	alarm(1);
}
void sig_alarm()
{
	sem_destroy(&s1);
	sem_destroy(&s2);
	exit(0);
}
int main()
{
	pthread_t pth1,pth2,pth3;
	sem_init(&s1,0,1);
	sem_init(&s2,0,0);
	pthread_create(&pth1,NULL,pthread1,NULL);
	pthread_create(&pth2,NULL,pthread2,NULL);
	pthread_create(&pth3,NULL,pthread3,NULL);
	pthread_join(pth1,NULL);
	pthread_join(pth2,NULL);
	return 0;
}
Linux编程三种常用线程同步