pthread最常用函数总结

通过一个多线程的关于生产者与消费者的例子,熟悉和掌握pthread最常用的一些函数。

下面代码演示了10个线程做为生产者,不间断的往vector中插入数据。一个线程做为消费者,不间断的读取vector中的数据并删除。当处理PRODUCE_NUM_MAX个数据时停止。临界区资源使用pthread_mutex_t进行保护。资源不可用时为了避免使用轮询监测资源是否可用,可使用pthread_cond_t条件变量进行休眠等待,直到条件满足时被唤醒。

#include <stdio.h>
#include <pthread.h>
#include <mutex>
#include <vector>
#include "utility.h"

#define PRODUCE_NUM_MAX 10000
#define PRODUCE_BUFF_SIZE 40
static pthread_once_t s_pot = PTHREAD_ONCE_INIT;
static pthread_key_t s_pk;
struct MsgVec
{
	pthread_mutex_t m_mutex;  //一个mutex可以对应多个cond,但是一个cond只能对应一个mutex.
	pthread_cond_t *m_pwcond; //写条件变量,动态申请的必须要调用destroy.
	pthread_cond_t m_rcond; //读条件变量.
	int m_maxid;
	vector<int> m_vec;
	
	MsgVec()
	{
		m_maxid = 0;
		m_mutex = PTHREAD_MUTEX_INITIALIZER;
		m_pwcond = (pthread_cond_t*)malloc(sizeof(pthread_cond_t));
		m_rcond = PTHREAD_COND_INITIALIZER;
		pthread_cond_init(m_pwcond, NULL);
	}
	~MsgVec()
	{
		if(m_pwcond)
		{
			pthread_cond_destroy(m_pwcond);
			free(m_pwcond);
			m_pwcond = NULL;
		}
		cout << "~Msg()" << endl;
	}
};
struct Pthread_info
{
	pthread_t m_pid;
	long long m_starttime = 0; //millisecond
	long long m_endtime = 0;
	long long m_cnt = 0;
	int m_fakewake = 0; //假唤醒次数。唤醒后条件不满足。
};

static pthread_mutex_t s_printmutex = PTHREAD_MUTEX_INITIALIZER;
void FreeKeyValue(void* data)
{
	pthread_mutex_lock(&s_printmutex);
	Pthread_info* pInfo = (Pthread_info*)data;
	cout << "pthread:" << pInfo->m_pid << " produce cnt:" << pInfo->m_cnt << " fake wake cnt:" <<pInfo->m_fakewake << " use time:" << pInfo->m_endtime - pInfo->m_starttime << endl;
	pthread_mutex_unlock(&s_printmutex);
	delete pInfo;
}

//创建线程私有数据key和对应的资源清理函数
void CreatePthreadKey()
{
	pthread_key_create(&s_pk, FreeKeyValue);
}

void* Produce(void* pData)
{
	pthread_detach(pthread_self()); //使自身线程分离,这样主线程不需要join回收资源.
	struct timespec tp;
	clock_gettime(_CLOCK_REALTIME, &tp);
	pthread_once(&s_pot, CreatePthreadKey); //所有线程只会调用一次.
	Pthread_info* pThreaddata = NULL;
	if(pthread_getspecific(s_pk) == NULL)// 各线程第一次pthread_getspecific反回NULL,然后设置自己的私有数据.
	{
		pThreaddata = new Pthread_info;
		pthread_setspecific(s_pk, pThreaddata);//设置线程私有数据
		pThreaddata->m_pid = pthread_self();
		pThreaddata->m_starttime = tp.tv_sec * 1000000LL + tp.tv_nsec / 1000ll;
		
	}
	MsgVec* pVec = (MsgVec*)pData;
	for(;;)
	{
		pthread_mutex_lock(&pVec->m_mutex);
		while (pVec->m_vec.size() >= PRODUCE_BUFF_SIZE && pVec->m_maxid <PRODUCE_NUM_MAX)
		{
			pthread_cond_wait(pVec->m_pwcond, &pVec->m_mutex);
			if(pVec->m_vec.size() >= PRODUCE_BUFF_SIZE)
				pThreaddata->m_fakewake++;
		}
		if(pVec->m_maxid >= PRODUCE_NUM_MAX)
		{
			pthread_mutex_unlock(&pVec->m_mutex);
			break;
		}
		for(int i = 0; i < 10; ++i)
			pVec->m_vec.push_back(pVec->m_maxid++);
		pThreaddata->m_cnt += 10;
		pthread_mutex_unlock(&pVec->m_mutex);
		//pthread_cond_signal,pthread_cond_broadcast可以在lock和unlock直接,也可以在unlock之外
		//能使用pthread_cond_signal的地方,替换为pthread_cond_broadcast总不会错
		//如果难以决定用哪个唤醒时,就使用broadcast方式。
		pthread_cond_signal(&pVec->m_rcond);
	}
	clock_gettime(_CLOCK_REALTIME, &tp);
	pThreaddata->m_endtime = tp.tv_sec * 1000000LL + tp.tv_nsec / 1000ll;
	pthread_exit(NULL);
}

void* Consume(void* pData)
{
	MsgVec* pVec = (MsgVec*)pData;
	long long consumecnt = 0;
	bool usebroadcast = true;
	for(;;)
	{
		pthread_mutex_lock(&pVec->m_mutex);
		while (pVec->m_vec.empty()) 
			pthread_cond_wait(&pVec->m_rcond, &pVec->m_mutex);
		if(pVec->m_vec.size() > PRODUCE_BUFF_SIZE || pVec->m_vec.size()%10 != 0)
			cout << "vec error. size:" << pVec->m_vec.size() << endl;
		consumecnt += pVec->m_vec.size();
		pVec->m_vec.erase(pVec->m_vec.begin(), pVec->m_vec.end());
		if(pVec->m_maxid == PRODUCE_NUM_MAX)
		{
			//如果不是broadcast模式,只会有一个producer退出,
			//为确保所有producert退出,这里需要唤醒所有producert.
			if(!usebroadcast)
				pthread_cond_broadcast(pVec->m_pwcond);
			pthread_mutex_unlock(&pVec->m_mutex);
			break;
		}
		else
		{
			if(usebroadcast)
				pthread_cond_broadcast(pVec->m_pwcond);
			else
				pthread_cond_signal(pVec->m_pwcond);
			pthread_mutex_unlock(&pVec->m_mutex);
		}
	}
	void* pRet = (void*)consumecnt;
	//1.如果有多个信息要返回,可以把这些信息放在一个结构中,返回结构的地址。
	//2.不要返回栈上变量的地址,因为线程退出后栈销毁,再访问的话就会出错。
	pthread_exit(pRet);
}



int main()
{
	MsgVec vec;
	pthread_t pt_producer[10], pt_consumer;
	
	for(int i = 0; i < arraysize(pt_producer); ++i)
		pthread_create(&pt_producer[i], NULL, Produce, &vec);
	
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setstacksize(&attr, 4*1024*1024);
	pthread_create(&pt_consumer, &attr, Consume, &vec);
	pthread_setconcurrency(arraysize(pt_producer) + 1);
	void* ret;
	pthread_join(pt_consumer, (void**)&ret);//用ret接收pt_consumer线程返回的信息的地址。
	
	pthread_mutex_lock(&s_printmutex);
	cout << "finish consume cnt:" << (long long)ret << endl;
	pthread_mutex_unlock(&s_printmutex);
	pthread_exit(0); //多线程编程时,主线程退出最好用pthread_exit。注意return和pthread_exit的区别。
}

讲解

1.代码11-37行,声明结构体MsgVec,里面的m_vec是生产者线程和消费者线程需要并发读写的容器。 m_mutex是保护m_vec的互斥锁,m_pwcond是是否可写的条件变量,m_rcond是是否可读的条件变量。pthread_mutex_t,pthread_cond_t,pthread_attr_t等使用前需要先初始化。初始化可以使用XXX_INITIALIZER进行赋值或者使用xxx_init()函数,如果是动态申请的变量就得使用xxx_init()函数并且最后释放需要调用xxx_destroy()函数。如果不是动态申请的xxx_destroy()调用不是必须的。

2.代码152-153行,创建10个生产者线程。传递vec的指针做为生产者线程启动函数Produce的参数。pthread_create函数的pthread_attr_t*参数指定为NULL,表示使用默认属性进行创建线程。

3.代码155-158行,创建1个消费者线程。传递vec的指针做为消费者线程启动函数Consume的参数。演示了通过pthread_attr_t指定线程的栈大小为4M。

4.代码159行,pthread_setconcurrency函数,通知系统其所需的并发级别。pthread_setconcurrency函数设定的并发度只是对系统的一个提示,系统并不保证请求的并发度一定会采用。

5.代码160-161行,通过ret接收pt_consumer线程返回的数据地址。Consume函数把数据元素个数强转为地址进行返回。如果有多个信息需要返回,那就把需要返回的信息声明到一个结构体中,然后Consume函数中动态申请这个结构体最后把该结构体的地址进行返回。注意不要返回栈上地址,因为线程结束后,栈被销毁,再访问销毁的栈地址是不合法的。

6.代码163-165行,打印消费者线程消费的元素个数。使用s_printmutex对输出进行保护,使本文中的各输出信息不会出现串行。

7.代码65行,线程通过调用pthread_detach使自己分离,从而其他线程不再需要进行pthread_join进行该线程结束时的资源清理。使线程分离可以调用pthread_detach或者创建线程时指定分离属性。可以通过调用pthread_attr_setdetachstate把分离属性设置到属性变量中。

8.代码68行,通过pthread_once创建生产者线程的私有数据key和对应的资源清理函数。pthread_once能确保所有线程调用但是只执行1次。一般用于初始化操作。

9.代码48-55行,函数FreeKeyValue是pthread_once初始化时设置的线程私有数据清理函数,程序结束时会打印出10条生产者线程的生产数据信息,并正确释放之前动态申请的Pthread_info内存。

10.代码69-77行,通过pthread_getspecific和pthread_setspecific获取和设置线程的私有数据。10条生产者线程第一次调用pthread_getspecific都会反回NULL,然后设置各自的私有数据。

11.代码79-101行,生产者线程不间断的测试生产条件,如果生产条件满足就往vector中插入10个数据,如果不满足生产条件就在pVec->m_pwcond这个条件变量上进行等待。

pthread_cond_wait用于阻塞当前线程,等待别的线程使用pthread_cond_signal()pthread_cond_broadcast来唤醒它 pthread_cond_wait() 必须与pthread_mutex配套使用。pthread_cond_wait()函数一进入wait状态就会自动release mutex。当其他线程通过pthread_cond_signal()pthread_cond_broadcast,把该线程唤醒,使pthread_cond_wait()通过(返回)时,该线程又自动获得该mutexpthread_cond_wait常与while条件监测搭配使用,使用while是避免[假唤醒].下面代码展示了常用的使用方法。

                                                    pthread_mutex_lock                                                                                                                                 while(条件)                                                                                                                                                     pthread_cond_wait 

                                                        .....操作临界区资源.....

                         pthread_cond_signal(1)可放在mutex锁内                                   pthread_mutex_unlock                                                   pthread_cond_signal(2)也可以放在mutex锁外

代码82-87行,进行while生产条件监测,如果线程被唤醒后不满足条件就累计pThreaddata->m_fakewake++的[假唤醒]次数。如果消费者是使用pthread_cond_broadcast来唤醒的那生产者线程将产生大量的假唤醒次数。因为pthread_cond_broadcast会唤醒所有消费者线程,当第PRODUCE_BUFF_SIZE/10个线程插入数据后,第PRODUCE_BUFF_SIZE/10+1个线程通过竞争取得pVec->m_mutex锁后退出pthread_cond_wait函数,此时pVec->m_vec.size() 已经等于 PRODUCE_BUFF_SIZE了,所以此次唤醒是[假唤醒]。pthread_cond_signal可以放在mutex锁内也可以放在mutex锁外。假设在线程A的mutex锁内调用pthread_cond_signal,唤醒了等待线程B。在线程A退出mutex锁前,操作系统调度到线程B执行,线程B此时在pthread_cond_wait内被唤醒,退出pthread_cond_wait时需要竞争再次锁上mutex,因为线程A还没释放所有又会导致线程B再被等待。从而会浪费一次线程调度切换。

12.代码113-138行,消费者线程不断进行消费条件监测,如果满足条件就清空vector的数据。如果条件不满足就等待在pVec->m_rcond上。能使用pthread_cond_signal的地方,替换为pthread_cond_broadcast总不会错,如果难以决定用哪个唤醒时,就使用broadcast方式。一个mutex可以对应多个条件,但是一个cond只能对应一个mutex。本例子演示了一个mutex对应了一个读条件和一个写条件。

13.代码166行,主线程使用pthread_exit结束,而不是return 0;这样能避免子线程所有输出未完成时,进程退出。pthread_exit和return的区别可见博文线程使用pthread_exit和return结束的区别_yadoufeng的博客-CSDN博客

运行结果

pthread:0x700001abf000 produce cnt:1020 fake wake cnt:99 use time:11510
pthread:0x700001dd1000 produce cnt:1140 fake wake cnt:108 use time:11270
pthread:0x700001bc5000 produce cnt:1080 fake wake cnt:85 use time:11483
pthread:0x700001ccb000 produce cnt:890 fake wake cnt:110 use time:11531
pthread:0x700001d4e000 produce cnt:850 fake wake cnt:96 use time:11457
pthread:0x700001ed7000 produce cnt:650 fake wake cnt:88 use time:11101
pthread:0x700001b42000 produce cnt:1390 fake wake cnt:63 use time:11526
pthread:0x700001e54000 produce cnt:990 fake wake cnt:96 use time:11162
pthread:0x700001c48000 produce cnt:1170 fake wake cnt:90 use time:11522
pthread:0x700001f5a000 produce cnt:820 fake wake cnt:91 use time:11022
finish consume cnt:10000
Program ended with exit code: 0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值