【Linux C与C++一线开发实践】之六 多线程高级编程

在多线程编程中,所有的线程都是并发、并行并且是异步执行的。这样就带来了线程间资源竞争的无序性,因此,我们需要引入同步机制来消除这种复制度并实现线程间的数据共享,以一致的顺序执行一组操作。场景:多个线程对同一临界区做操作。

利用POSIX多线程API函数进行线程同步

POSIX提供了3种方式进行线程同步,即互斥锁、读写锁和条件变量
1.互斥锁
互斥锁的原理是同一时刻,只允许一个线程对临界区进行访问。其工作流程是:初始化一个互斥锁,在进入临界区前把互斥锁加锁,退出临界区时把互斥锁解锁,最后不用了就销毁它,这4步。
下面我们来看一个使用互斥锁的卖货程序:

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

int a = 200;  //当前货物价值
int b = 100;  //当前现金

pthread_mutex_t lock;  //定义一个全局的互斥锁

// 伙计卖货线程
void* ThreadA(void *) 
{
	while(1) {
		pthread_mutex_lock(&lock); // 上锁
		a -= 50; //卖出去50元货物
		b += 50; //收回50元钱
		pthread_mutex_unlock(&lock); // 解锁
	}
}
// 老板对账线程
void* ThreadB(void *) 
{
	while(1) {
		pthread_mutex_lock(&lock);
		printf("%d\n", a + b);
		pthread_mutex_unlock(&lock);
		sleep(1);
	}
}

int main()
{
	pthread_t tida, tidb;
	pthread_mutex_init(&lock, NULL); // 初始化互斥锁
	pthread_create(&tida, NULL, ThreadA, NULL);
	pthread_create(&tidb, NULL, ThreadB, NULL);
	pthread_join(tida, NULL);
	pthread_join(tidb, NULL);

	pthread_mutex_destroy(&lock); // 销毁互斥锁
	return 1;
}

打印:300、300、300、300.。。。

2.读写锁
我们来想想使用互斥锁存在的一个问题:如果很多线程对同一个临界区访问只是只读而不用修改数据,那么如果使用互斥锁这些线程就不能并行去访问了。而读写锁刚好就可以用来解决这个问题,读写锁,分为读锁与写锁两种上锁类型,如果当前临界区是上的读锁那么其它线程还是可以用读锁获得此临界区,直到有线程对其用了写锁,那么此临界区才会阻塞其它线程访问。
需要注意,在并行性上面读写锁确实要比互斥锁好,但是并不是速度一定比互斥锁快,读写锁更复杂,系统开销更大。读写锁并发性好,线程越多,并发越大优势明显。
其工作流程和互斥锁类似,也是初始化、上锁、解锁、销毁。

// 初始化
int pthread_rwlock_init(xx, xx);
// 读/写模式上锁
int pthread_rwlock_rdlock(xx);
int pthread_rwlock_wrlock(xx);
//解锁
int pthread_rwlock_unlock(xx);
//销毁
int pthread_rwlock_destroy(xx);

3.条件变量
我们来看看这样一个场景:线程A需要等某个条件成立才能继续往下执行,现在这个条件不成立,线程A就阻塞等待,而线程B在执行过程中使这个条件成立了,于是唤醒线程A继续执行。同步机制之一的条件变量就是用在这种场合,它可以让一个线程等待条件变量的条件而挂起(pthread_cond_wait),另一个线程在条件成立后向挂起的线程发送条件成立的信号(pthread_cond_signal)。这里注意,为了防止多个线程同时请求pthread_cond_wait形成竞争,因此条件变量必须和一个互斥锁配合使用。
条件变量工作流程:初始化、等待条件变量、唤醒等待条件变量的线程、条件变量销毁。
下面来看一个使用条件变量的示例:
找出1~20中能整除3的整数

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

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;/*初始化互斥锁*/
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;/*初始化条件变量*/

void *thread1(void *);
void *thread2(void *);

int i = 1;
int main(void)
{
	pthread_t t_a;
	pthread_t t_b;

	pthread_create(&t_a, NULL, thread2, (void *)NULL);//创建线程t_a
	pthread_create(&t_b, NULL, thread1, (void *)NULL); //创建线程t_b
	pthread_join(t_b, NULL);/*等待进程t_b结束*/
	pthread_mutex_destroy(&mutex);
	pthread_cond_destroy(&cond);
	exit(0);
}

void *thread1(void *junk)
{
	for (i = 1; i <= 20; i++)
	{
		pthread_mutex_lock(&mutex);//锁住互斥量
		if (i % 3 == 0)
			pthread_cond_signal(&cond); //唤醒等待条件变量cond的线程
		else       
			printf("thead1:%d\n", i); //打印不能整除3的i
		pthread_mutex_unlock(&mutex);//解锁互斥量

		sleep(1);
	}

}

void *thread2(void *junk)
{
	while (i < 20)
	{
		pthread_mutex_lock(&mutex);

		if (i % 3 != 0)
			pthread_cond_wait(&cond, &mutex);//等待条件变量
		printf("------------thread2:%d\n", i); //打印能整除3的i
		pthread_mutex_unlock(&mutex);

		sleep(1);
		i++;
	}

}

thead1:1
thead1:2
------------thread2:3
thead1:5
------------thread2:6
thead1:8
------------thread2:9
thead1:10
------------thread2:12
thead1:13
------------thread2:15
thead1:16
------------thread2:18
thead1:19

C++11/14中的线程同步

语言级别提供两个方式:互斥锁和条件变量。基本原理和传统线程同步一致这里就不再最赘述。

线程池

池技术在连续产生大量并发任务的场合使用很多。是一种常用的优化手段,避免大量对象反复的创建和销毁影响性能。
实现过程: 线程池就是有一堆已经创建好的线程,初始都处于空闲等待状态,当有新任务需要处理时,就从这堆线程中取一个空闲等待的线程来处理该任务,当任务处理完毕后,就再次把该线程放回池中,状态置为空闲,供后面任务继续使用。当池中所有线程都处于忙碌状态时,此时根据需要可以创建一个新线程并置入池中,或者通知任务当前线程池所有线程都在忙,等待片刻再尝试。

下面用C++实现一个简单的线程池
thread_pool.h


#ifndef __THREAD_POOL_H
#define __THREAD_POOL_H

#include <vector>
#include <string>
#include <pthread.h>

using namespace std;

/*执行任务的类:设置任务数据并执行*/
class CTask {
protected:
	string m_strTaskName;   //任务的名称
	void* m_ptrData;    //要执行的任务的具体数据

public:
	CTask() = default;
	CTask(string &taskName)
		: m_strTaskName(taskName)
		, m_ptrData(NULL) {}
	virtual int Run() = 0;
	void setData(void* data);   //设置任务数据
  
	virtual ~CTask() {}
    
};

/*线程池管理类*/
class CThreadPool {
private:
	static vector<CTask*> m_vecTaskList;    //任务列表
	static bool shutdown;   //线程退出标志
	int m_iThreadNum;   //线程池中启动的线程数
	pthread_t *pthread_id;
  
	static pthread_mutex_t m_pthreadMutex;  //线程同步锁
	static pthread_cond_t m_pthreadCond;    //线程同步条件变量
  
protected:
	static void* ThreadFunc(void *threadData);  //新线程的线程回调函数
	static int MoveToIdle(pthread_t tid);   //线程执行结束后,把自己放入空闲线程中
	static int MoveToBusy(pthread_t tid);   //移入到忙碌线程中去
	int Create();   //创建线程池中的线程
  
public:
	CThreadPool(int threadNum);
	int AddTask(CTask *task);   //把任务添加到任务队列中
	int StopAll();  //使线程池中的所有线程退出
	int getTaskSize();  //获取当前任务队列中的任务数
};

#endif

thread_pool.cpp

#include "thread_pool.h"
#include <cstdio>

void CTask::setData(void* data) {
	m_ptrData = data;
}

//静态成员初始化
vector<CTask*> CThreadPool::m_vecTaskList;
bool CThreadPool::shutdown = false;
pthread_mutex_t CThreadPool::m_pthreadMutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t CThreadPool::m_pthreadCond = PTHREAD_COND_INITIALIZER;

//线程管理类构造函数
CThreadPool::CThreadPool(int threadNum) {
	this->m_iThreadNum = threadNum;
	printf("I will create %d threads.\n", threadNum);
	Create();
}

//线程回调函数
void* CThreadPool::ThreadFunc(void* threadData) {
	pthread_t tid = pthread_self();
	while (1)
	{
		pthread_mutex_lock(&m_pthreadMutex);
		//如果队列为空,等待新任务进入任务队列
		while (m_vecTaskList.size() == 0 && !shutdown)
			pthread_cond_wait(&m_pthreadCond, &m_pthreadMutex);
        
		//关闭线程
		if (shutdown)
		{
			pthread_mutex_unlock(&m_pthreadMutex);
			printf("[tid: %lu]\texit\n", pthread_self());
			pthread_exit(NULL);
		}
        
		printf("[tid: %lu]\trun: ", tid);
		vector<CTask*>::iterator iter = m_vecTaskList.begin();
		//取出一个任务并处理之
		CTask* task = *iter;
		if (iter != m_vecTaskList.end())
		{
			task = *iter;
			m_vecTaskList.erase(iter);
		}
        
		pthread_mutex_unlock(&m_pthreadMutex);
        
		task->Run();    //执行任务
		printf("[tid: %lu]\tidle\n", tid);
        
	}
    
	return (void*)0;
}

//往任务队列里添加任务并发出线程同步信号
int CThreadPool::AddTask(CTask *task) { 
	pthread_mutex_lock(&m_pthreadMutex);    
	m_vecTaskList.push_back(task);  
	pthread_mutex_unlock(&m_pthreadMutex);  
	pthread_cond_signal(&m_pthreadCond);    
    
	return 0;
}

//创建线程
int CThreadPool::Create() { 
	pthread_id = new pthread_t[m_iThreadNum];
	for (int i = 0; i < m_iThreadNum; i++)
		pthread_create(&pthread_id[i], NULL, ThreadFunc, NULL);
        
	return 0;
}

//停止所有线程
int CThreadPool::StopAll() {    
    //避免重复调用
	if (shutdown)
		return -1;
	printf("Now I will end all threads!\n\n");
    
	//唤醒所有等待进程,线程池也要销毁了
	shutdown = true;
	pthread_cond_broadcast(&m_pthreadCond);
    
	//清楚僵尸
	for (int i = 0; i < m_iThreadNum; i++)
		pthread_join(pthread_id[i], NULL);
    
	delete[] pthread_id;
	pthread_id = NULL;
    
	//销毁互斥量和条件变量
	pthread_mutex_destroy(&m_pthreadMutex);
	pthread_cond_destroy(&m_pthreadCond);
    
	return 0;
}

//获取当前队列中的任务数
int CThreadPool::getTaskSize() {    
	return m_vecTaskList.size();
}

main.cpp

#include "thread_pool.h"
#include <cstdio>
#include <stdlib.h>
#include <unistd.h>

class CMyTask : public CTask {
public:
	CMyTask() = default;
	int Run()
	{
		printf("%s\n", (char*)m_ptrData);
		int x = rand() % 4 + 1;
		sleep(x);
		return 0;
	}
	~CMyTask() {}
}

int main() {
	CMyTask taskObj;
	char szTmp[] = "hello!";
	taskObj.setData((void*)szTmp);
	CThreadPool threadpool(5);

	for(int i = 0; i < 10; i++) {
		threadpool.AddTask(&taskObj);
	}

	while(1) {
		printf("There are still %d tasks need to handle \n", threadpool.getTaskSize());
		if(threadpool.getTaskSize() == 0) {
			if(threadpool.StopAll() == -1) {
				printf("Thread pool clear, exit.\n");
				exit(0);
			}
		}
		sleep(2);
		printf("2 seconds later...\n");
	}
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值