线程同步

1、互斥量

1、什么是互斥量
互斥量(mutex)从概念上来说类似于一个二进制信号量,即初始值为1的信号量。互斥量被获取之后就不能再被获取,因此对互斥体的获取和释放操作常常称为加锁和解锁操作。
互斥量只能由获取它的线程进行释放,如果违反这一原则,则结果是未定义的。
互斥量从本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥量。
互斥量操作类似信号量,(在信号量为1的时候,p操作和v操作对应互斥量的加锁和解锁)

2、互斥量的初始化
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
第一个参数 :mutex参数指向要初始化的互斥量。
第二个参数 :attr参数指向一个描述互斥量属性的结构体。attr参数可以为NULL,表示使用默认属性。

3、互斥量的操作函数(加锁和解锁)
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
pthread_mutex_lock()用于对mutex参数指向的互斥量进行加锁。如果这时互斥量已经被锁,则调用这个函数的线程将被阻塞,直到互斥量成为未锁状态。函数返回时,表示这个互斥量已经变为已锁状态,同时,函数的调用者成为这个互斥量的拥有者。
pthread_mutex_trylock()也用于对mutex参数指向的互斥量进行加锁。如果这时互斥量已经被锁,则函数以错误状态返回。
pthread_mutex_unlock()用于对mutex参数指向的互斥量进行解锁。如果这时互斥量是未锁状态或不是当前线程所拥有的,则结果未定义。 因此,互斥量必须在同一线程上成对出现。

4、销毁
互斥量不用以后,应该使用下面的函数进行销毁:
int pthread_mutex_destroy(pthread_mutex_t *mutex);

5、生产者与消费者模型改进(使用互斥量加锁)

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

struct Data
{
	char *c[11];				//数据存放
	sem_t sem_full;				//满信号量
	sem_t sem_empty;			//空信号量
	int count;					//数据个数
}data;							//全局变量

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *produce()					//生产者工作函数
{
	char *buf[] = {"绝地求生","真三国无双","巫师3","战地1","黑魂"};
	int len = sizeof(buf)/sizeof(buf[1]);
	
	while(1)
	{
		usleep(100000*(rand()%10+1));
		sem_wait(&(data.sem_empty));				//p操作
		pthread_mutex_lock(&mutex);					//上锁
		
		data.c[data.count] = buf[rand()%len];
		data.count++;
		
		pthread_mutex_unlock(&mutex);				//解锁
		sem_post(&(data.sem_full));					//V操作
	}
}

void *consumption(void *i)					//消费者工作函数
{
	
	long num = (long)i;
	while(1)
	{	
		usleep(100000*(rand()%10+1));	
		sem_wait(&(data.sem_full));					//p操作
		pthread_mutex_lock(&mutex);					//上锁
		
		char *tmp = data.c[rand()%data.count];
		printf("%ld 号游玩了\"%s\",还有%d个游戏\n",num,tmp,data.count-1);	
		data.c[rand()%data.count] = data.c[data.count-1];
		data.c[data.count] = tmp;
		data.count--;
		
		pthread_mutex_unlock(&mutex);					//解锁
		sem_post(&(data.sem_empty));				//V操作
	}
}

int main(int argc,char **argv)
{
	long i;
	srand((unsigned int)time(NULL));
	pthread_t thread;
	for(i = 1;i < 4;i++)				// 3个消费者
	{
		pthread_create(&thread,NULL,consumption,(void *)i);
		pthread_detach(thread);
	}
	for(i = 1;i < 4;i++)				// 3个消费者
	{
		pthread_create(&thread,NULL,produce,NULL);
		pthread_detach(thread);
	}
	
	sem_init(&(data.sem_full),0,0);				//满初始化
	sem_init(&(data.sem_empty),0,10);			//空初始化
	
	pthread_exit(NULL);
	
	return 0;
}

2、条件变量

1、为什么有条件变量
假设有这样一种情况:线程正在等待共享数据内某个条件出现,这时必须先解锁,否则其他线程不可能更改共享数据。一种实现方式是,可以循环检测共享数据,但是在检测前要加锁,检测后又要解锁,这样效率会很低。
因此,在这种情况下,需要一种方法,使得当线程在等待某些条件的满足时进入睡眠状态,一旦条件满足,线程就应该被唤醒继续执行。这个方法就是使用POSIX条件变量。

2、条件变量的初始化
int pthread_cond_init(pthread_cond_t *cond,const pthread_condattr_t *attr);
第一个参数 :cond参数指向要初始化的条件变量,
第二个参数 :attr参数指向描述条件变量属性的结构体。attr可以为NULL,表示使用默认属性。

3、条件变量的使用(等待和触发)
(1)等待的实现
int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex, const struct timespec *abstime);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
这些函数都有一个指向互斥量的参数mutex,说明条件变量必须与互斥量搭配使用。 调用这些函数时,首先,指定的互斥量将被释放,然后线程将阻塞,等待条件变量的触发。函数返回前,互斥量重新被线程获取。
pthread_cond_timedwait与pthread_cond_wait的区别在于,前者有一个由abstime指定的超时时间限制,当线程阻塞的时间超过了这个时间就会自动醒来。
(2)触发(广播)
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_signal可以唤醒一个或多个正在等待cond参数所指向的条件变量的线程,而pthread_cond_broadcast则可以唤醒全部正在等待cond参数所指向的条件变量的线程。

注意:POSIX标准只规定pthread_cond_signal函数唤醒至少一个睡眠中的线程,并没有规定只唤醒一个。

4、条件变量的销毁
nt pthread_cond_destroy(pthread_cond_t *cond);

5、使用

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

int resoure;
pthread_cond_t cond1;			//条件变量

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;;

void *produce()					//生产者工作函数
{
	while(1)
	{
		usleep(100000*(rand()%10+1));
		pthread_mutex_lock(&mutex);					//上锁
		
		resoure += 2; 
		
		pthread_cond_broadcast(&cond1);				//有资源的时候广播
		pthread_mutex_unlock(&mutex);				//解锁
	}
}

void *consumption(void *i)					//消费者工作函数
{
	
	long num = (long)i;
	while(1)
	{	
		usleep(100000*(rand()%10+1));	
		pthread_mutex_lock(&mutex);					//上锁
		
		while(resoure <= 0)
		{
			pthread_cond_wait(&cond1,&mutex);		//条件不满足的时候等待,同时解锁,满足的时候会和其他的线程进行抢锁
		}
		
		resoure--;
		printf("%ld号消费者使用,剩余%d个资源\n",num,resoure);

		pthread_mutex_unlock(&mutex);					//解锁
	}
}

int main(int argc,char **argv)
{
	long i;
	srand((unsigned int)time(NULL));
	pthread_t thread;
	for(i = 1;i < 4;i++)				// 3个消费者
	{
		pthread_create(&thread,NULL,consumption,(void *)i);
		pthread_detach(thread);
	}
	
	pthread_create(&thread,NULL,produce,NULL);
	pthread_detach(thread);
	
	pthread_cond_init(&cond1,NULL);			//初始化条件变量
	
	pthread_exit(NULL);
	
	return 0;
}

3、信号量

1、初始化一个未命名的信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
第一个参数:指向需要初始化的信号量(sem_t类型)。是一个外部传参的参数
第二个参数:表明信号量是在一个进程的多个线程之间共享还是在多个进程之间共享。若pshared为0,信号量被一个进程的多个线程共享,此时应该将信号量(sem_t)置于所有线程可见的位置(全局变量或动态分配)。
第三个参数:指定信号量的初始值。

执行成功返回0,出错返回-1,并设置errno。

2、信号量控制
int sem_post(sem_t *sem); // v操作
int sem_wait(sem_t *sem); // p操作
sem_post的作用是以原子操作的方式给信号量的值加1
sem_wait函数以原子操作的方式将信号量的值减1,但它会等待直到信号量有个非零值才会开始减法操作。例如,对值为2的信号量调用sem_wait,线程将继续执行,但信号量的值会减到1。如果对值为0的信号量调用sem_wait,这个函数就会等待,直到有其它线程增加了该信号量的值使其不再为0为止。
如果两个线程同时在sem_wait函数上等待同一个信号量变为非零值,那么当该信号量被第三个线程增加1时,只有其中一个等待线程将开始对信号量减1,然后继续执行,另外一个线程还将继续等待。
补充:还有另外一个信号量函数sem_trywait,它是sem_wait的非阻塞版本。
3、信号量销毁
int sem_destroy(sem_t *sem);
作用:用完信号量后对它进行清理,清理该信号量所拥有的资源。如果你试图清理的信号量正被一些线程等待,就会收到一个错误。
4、生产者与消费者模型

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

struct Data
{
	char c[20];					//数据存放
	sem_t sem_full;				//满信号量
	sem_t sem_empty;			//空信号量
}data;							//全局变量

void *produce()					//生产者工作函数
{
	char *buf[] = {"绝地求生","真三国无双","巫师3","战地1","黑魂"};
	int len = sizeof(buf)/sizeof(buf[1]);
	
	while(1)
	{
		sem_wait(&(data.sem_empty));					//p操作
		
		strcpy(data.c,buf[rand()%len]);
		usleep(100000*(rand()%10+1));
		sem_post(&(data.sem_full));					//V操作
	}
}

void *consumption(void *i)					//消费者工作函数
{
	
	long num = (long)i;
	while(1)
	{	
		sem_wait(&(data.sem_full));					//p操作
		printf("%ld 号消费者游玩了 \"%s\"\n",num,data.c);
		usleep(100000*(rand()%10+1));
		sem_post(&(data.sem_empty));					//V操作
	}
}

int main(int argc,char **argv)
{
	long i;
	srand((unsigned int)time(NULL));
	pthread_t thread;
	for(i = 1;i < 20;i++)				// 3个消费者
	{
		pthread_create(&thread,NULL,consumption,(void *)i);
		pthread_detach(thread);
	}
	
	pthread_create(&thread,NULL,produce,NULL);
	pthread_detach(thread);
	
	sem_init(&(data.sem_full),0,0);				//满初始化
	sem_init(&(data.sem_empty),0,1);			//空初始化
	
	pthread_exit(NULL);
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值