APUE读书笔记——线程同步(条件变量,屏障)

什么是同步? 就是让线程之间按照一定的顺序去执行,例如线程A生产了一个物品,线程B才可以去执行消费。


为什么不能直接用锁去做同步?

例如设定锁X,锁Q

线程B要消费一个物品,则对生产队列做检查(先对队列加锁保护),即对X加锁,若X已被锁,则阻塞。

即 加锁Q   加锁X  消费    解锁Q  

当线程A生产了一个物品时, 将锁X解锁, 线程B唤醒,开始消费。

即 加锁Q  生产  解锁X   解锁Q

但是这有一个问题,如果线程A连续生产了很多物品, 之后不再生产,那么B去消费时,是没有必要加锁的,直接从队列里去取即可。

所以B的逻辑应该为

加锁Q    检查队列是否为空,若为空,加锁X      消费      解锁Q

但这时还存在一个大问题:  当线程B卡在加锁X时,队列Q被上锁了, 那么后来想再生产时,就进不去了!


所以这时候我们引入了条件变量X。

当线程B发现队列为空时, 自动解锁Q,并等待条件变量X为“可行“。

如果当线程A生产了一个物品时,就给条件变量X赋值, 于是线程B唤醒,并立刻自动加锁Q,开始消费

#include <stdlib.h>
#include <pthread.h>
struct msg{
	struct msg *m_next;
};
struct msg *workq;
pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;

void process_msg(void){
	struct msg *mp;
	for(;;){
		pthread_mutex_lock(&qlock);
		//如果队列为空,则对qlock解锁,并等待队列中有消息插入
		//否则直接取队头。
		while(workq == NULL)
			pthread_cond_wait(&qready, &qlock);
		mp = workq;       //从队头取出消息
		workq = mp->m_next;
		pthread_mutex_unlock(&qlock);
		/*处理消息*/
	}
}

void enqueue_msg(struct msg *mp){
	//给队列加锁
	pthread_mutex_lock(&qlock);
	mp->m_next = workq;  //插入一个消息到队头
	workq = mp;
	pthread_mutex_unlock(&qlock);
	//告诉线程,qread准备完毕了,你可以读了。
	pthread_cond_signal(&qread);
}

为什么要用while(workq != NULL)  而不是 if(workq != NULL)        ?

因为线程唤醒和给队列Q加锁,可能不是同一时刻发生。

可能当你唤醒时,另一个线程又取走了消息, 于是当你给队列加锁后,可能队列依然为空。

故需要while循环, 当在while循环里发现队列不为空时, 队列已经被你上锁了。



二、屏障

如果要多个线程,同时等待一个条件,并且在等待结束后同时继续后面的工作

即例如:  一群人做完自己的工作后,必须等所有人的工作都完成了,主管才分配后的工作。

那么这种情况可以用屏障barrier。


书里用的例子是多线程排序。

把一个很大的数组划分为8块,

其中每块各自用堆排序进行排序。

当全部排完后,进行合并,用归并合并的方式,每次从8个数组头中选最小的取出,放入结果数组。

好处是在多核系统中,能够较好地利用线程效率。

#include <stdlib.h>
#include <pthread.h>
#include <limits.h>
#include <sys/time.h>

#define NTHR 8
#define NUMNUM 8000000L
#define TNUM (NUMNUM/NTHR)

long nums[NUMNUM];
long snums[NUMNUM];

pthread_barrier_t b;
#ifdef SOLARIS
#define heapsort qsort
#else 
	extern int heapsort(void *, size_t,size_t,
			int(*)(const void *, const void *));
#endif

//把对应地址区域的数据视作long类型,去比较大小。
int complong(const void *arg1, const void *arg2){
	long l1 = *(long *)arg1;
	long l2 = *(long *)arg2;
	if(l1 == l2)
		return 0;
	else if(l1 < l2)
		return -1;
	else 
		return 1;
	
}
void thr_fn(void *arg){
	long idx = (long)arg;
	
	//以nums[idx]为起点,长度为TNUM的数据区域进行排序
	heapsort(&nums[idx], TNUM, sizeof(long), complong);
	//排序完后,等待大家一起返回。
	pthread_barrier_wait(&b);
	return ((void *)0);
}

void merge(){
	long idx[NTHR];
	long i, minidx, sidx, num;
	for (i = 0;i < NTHR; i++)
		idx[i] = i * TNUM; //偏移
	//idx指那个小排序数组的当前指针
	//数组中每被取走一个,idx加一。	
	for (sidx = 0;sidx < NUMNUM; sidx++){
		num = LONG_MAX;
		//从NTHR个队列的队头中,找到最小的那个,并取走
		for(i = 0;i < NTHR; i++){
			//注意越界限制
			if( (idx[i] < (i+1)*TNUM) && (nums[idx[i]] < num)){
				num = nums[idx[i]];
				minidx = i;
			}
		}
		//把选中的数字放入snums,即结果数组。
		snums[sidx] = nums[idx[minidx]];
		idx[minidx] ++;
	}
}

int main(){
	unsigned long i;
	struct timeval start,end;
	long long startusec, endusec;
	double elapsed;
	int err;
	pthread_t tid;
	
	srandom(1);
	for( i = 0;i < NUMNUM;i++)
		nums[i] = random();
	
	gettimeofday(&start, NULL);
	pthread_barrier_init(&b, NULL, NTHR+1); //NTHR+1是线程数量
	for( i = 0; i < NTHR; i++){
		//传入线程的参数是void类型的,所以要做类型转化
		//传入的参数是每个线程要处理的数据的起始点。
		err = pthread_create(&tid, NULL, thr_fn, (void *)(i * TNUM));
		if(err != 0)
			err_exit(err, "can't create thread");
	}
	pthread_barrier_wait(&b);
	//统一处理完毕,到达屏障之后,再进行合并。
	merge();
	
	gettimeofday(&end, NULL);
	startusec = start.tv_sec * 1000000 + start.tv_usec;
	endusec = end.tv_sec * 1000000 + end.tv_usec;
	elapsed = (double)(endusec - startusec) / 1000000.0;
	printf("sort took %.4f seconds\n", elapsed);
	for( i = 0;i < NUMNUM; i++)
		printf("%ld\n", snums[i]);
	exit(0);
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值