Linux 生产者和消费者问题

(摘录与APUE)pthread_self函数获取自己的线程ID,而不是通过pid去拿的,如果新线程在主线程调用pthread_create返回之前就运行了,那么在新线程中使用pid将是不正确的ID

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


pthread_t pid = 1;

void *__start_routine(void *nul)
{
    printf("%lu\n",(unsigned long)pthread_self());
    
    //be careful of pid
    printf("__start_routine() pid_t = %lu\n",(unsigned long)pid);
}

int main(void)
{


    pthread_create(&pid,NULL,__start_routine,NULL);
    printf("main() pid_t = %lu\n",pid);
    printf("Hello World!\n");

    pthread_join(pid,NULL);
    return 0;
}


pthread_join等待子线程的退出,并拿到子线程退出的返回值


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


/*
	int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,
	(void*)(*start_rtn)(void*),void *arg);
*/
void *_thread_main(void *arg)
{
	printf("_thread_main run\n");
	pthread_exit(arg);
	//return arg;
}

int main(int argc,char *argv[])
{
	pthread_t tid;
	int idata = 123;
	
	int res = pthread_create(&tid,NULL,_thread_main,(void *)idata);
	if(res < 0)
	{
		printf("pthread_create error!!!\n");
		return -1;
	}
	printf("res = %d\n",res);
	void *p = NULL;
	pthread_join(tid,&p);
	int i = (int)p;
	printf("i = %d\n",i);
	
	return 0;
}



pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

int
makethread(void *(*fn)(void *), void *arg)
{
	int				err;
	pthread_t		tid;
	pthread_attr_t	attr;

	err = pthread_attr_init(&attr);
	if (err != 0)
		return(err);
	err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	if (err == 0)
		err = pthread_create(&tid, &attr, fn, arg);
	pthread_attr_destroy(&attr);
	return(err);
}

 pthread_cond_timedwait

1.多个线程操作临界区要使用线程锁

2.当某个线程进入临界区后  该线程发现条件不满足,该线程一直霸占着锁,所以需要该线程主动放弃这个锁,并等待条件达到被唤醒。故出现了  pthread_cond_wait(&g_cond,&g_mutex);      让这个线程锁在某个条件上等待

3.pthread_cond_wait(&g_cond,&g_mutex);背后隐藏的事情

  1. 拿到锁的线程,把锁暂时丢掉(因为该函数中传入了g_mutex),相当于解锁
  2. 线程休眠,进行等待
  3. 线程等待通知,要醒来(重新获取锁)

/usr/lib/x86_64-linux-gnu/libpthread.so 线程库 将上面的三步操作做成了原子操作

总结:pthread_cond_wait  条件等待,我在某个条件要主动让出锁,让其他线程可以获取锁,等待其他线程唤醒我,我再获取锁

pthread_cond_signal(&g_cond)通知条件满足


很多人喜欢在pthread_mutex_lock()和pthread_mutex_unlock()之间调用 pthread_cond_signal或者pthread_cond_broadcast函数,
从逻辑上来说,这种使用方法是完全正确的。但是在多线程 环境中,这种使用方法可能是低效的。
posix1标准说,pthread_cond_signal与pthread_cond_broadcast无需考虑调用线程是否是mutex的拥有者,也就是所,可以在lock与unlock以外的区域调用。
如果我们对调用行为不关心,那么请在lock区域之外调用吧。这里举个例子:

我们假设系统中有线程1和线程2,他们都想获取mutex后处理共享数据,再释放mutex。请看这种序列:

1)线程1获取mutex,在进行数据处理的时候,线程2也想获取mutex,但是此时被线程1所占用,线程2进入休眠,等待mutex被释放。

2)线程1做完数据处理后,调用pthread_cond_signal()唤醒等待队列中某个线程,在本例中也就是线程2。线程1在调用 pthread_mutex_unlock()前,因为系统调度的原因,线程2获取使用CPU的权利,那么它就想要开始处理数据,但是在开始处理之 前,mutex必须被获取,很遗憾,线程1正在使用mutex,所以线程2被迫再次进入休眠。

3)然后就是线程1执行pthread_mutex_unlock()后,线程2方能被再次唤醒

从这里看,使用的效率是比较低的,如果再多线程环境中,这种情况频繁发生的话,是一件比较痛苦的事情。
所以觉得,如果程序不关心线程可预知的调度行为,那么最好在锁定区域以外调用他们吧


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

// gcc o test multi_producer_consumer.c -pthread

//定义锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//定义条件
pthread_cond_t  cond = PTHREAD_COND_INITIALIZER;

#define CUSTOMER_COUNT 2
#define PRODUCER_COUNT 1

int g_count = 0;


void * thread_consumer(void *arg)
{
	int inum = (int)arg;
	
	while(1)
	{
		pthread_mutex_lock(&mutex);
		while(g_count == 0)//醒来以后需要重新判断条件是否满足,如果不满足再次等待
		{
			printf("thread_consumer  %d: waiting............\n",inum);
			pthread_cond_wait(&cond,&mutex);
			printf("thread_consumer  %d: wake up............\n",inum);
		}
		//消费产品
		printf("thread_consumer  %d: g_count %d\n",inum,g_count);
		g_count--;
		pthread_mutex_unlock(&mutex);
		sleep(1);
	}
	pthread_exit(0);
}

void * thread_producer(void *arg)
{
	int inum = (int)arg;
	while(1)
	{
		pthread_mutex_lock(&mutex);
		g_count++;
		//生产产品
		printf("thread_producer  %d :g_count %d\n",inum,g_count);
		if(g_count > 0)
		{
			pthread_cond_signal(&cond);
		}
		pthread_mutex_unlock(&mutex);
		sleep(1);
	}
	pthread_exit(0);
}

int main()
{
	int i = 0;
	pthread_t tidArray[CUSTOMER_COUNT+PRODUCER_COUNT];
	for(i = 0;i < CUSTOMER_COUNT ;i++)
	{
		pthread_create(&tidArray[i],NULL,thread_consumer,(void *)i);
	}
	
	for(i = 0;i < PRODUCER_COUNT ;i++)
	{
		pthread_create(&tidArray[i+CUSTOMER_COUNT],NULL,thread_producer,(void *)i);
	}
	
	for(i = 0;i < CUSTOMER_COUNT + PRODUCER_COUNT ;i++)
	{
		pthread_join(tidArray[i],NULL);
	}
	return 0;
}

结果:

thread_consumer  0: waiting............                消费者0抢到线程,进行条件等待
thread_consumer  1: waiting............               消费者1抢到线程,进行条件等待
thread_producer  0 :g_count 1                       生产者0抢到线程,生产,然后发送信号
thread_consumer  0: wake up............           消费者0被唤醒
thread_consumer  0: g_count 1                   消费者0消费

thread_producer  0 :g_count 1                   生产者0抢到线程,生产,然后发送信号
thread_consumer  0: g_count 1           消费者0抢得线程,发现g_count == 1 ,不会进入while(g_count == 0)循环,直接消费掉,没有被正在条件等待的消费者1拿到

thread_consumer  1: wake up............    消费者1醒来
thread_consumer  1: waiting...........               while(g_count == 0)继续进入waiting状态

thread_producer  0 :g_count 1
thread_consumer  0: g_count 1
thread_consumer  1: wake up............
thread_consumer  1: waiting............


thread_producer  0 :g_count 1
thread_consumer  0: g_count 1
thread_consumer  1: wake up............
thread_consumer  1: waiting............

thread_producer  0 :g_count 1                    生产者0抢到线程,生产,然后发送信号
thread_consumer  1: wake up...........         .消费者1拿到被唤醒
thread_consumer  1: g_count 1                       消费者1消费
thread_consumer  0: waiting............                       消费者0进行条件等待

关于:while(g_count == 0)//醒来以后需要重新判断条件是否满足,如果不满足再次等待

Spurious wakeup现象是在条件变量使用中出现的,即一个线程可能即使没有条件变量signal的时候也会被唤醒(a thread might be awoken from its waiting state even though no thread signaled the condition variable.)这样会导致wait线程误以为条件成立,因此,正确做法是通过while判断相应的条件,如下所示:

/* In any waiting thread: */
while(!buf->full)
	wait(&buf->cond, &buf->lock);
 
/* In any other thread: */
if(buf->n >= buf->size){
	buf->full = 1;
	signal(&buf->cond);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值