多线程开发在实际工作中十分重要,在开发C++程序时,一般在吞吐量、并发、实时性上有较高的要求。因此,本文旨在讲解多线程的基本概念,以及利用C++编写一种生产者-消费者模型的多线程实例。
基本概念
通常,并发编程有两种模式,分别是多进程并发和多线程并发。多进程并发指的是开辟多个独立且只包含一个线程的进程,进程间互相通信,共同完成一个任务。但是,由于操作系统对进程提供了大量的保护机制,不同进程之间的数据不能互相修改,一方面,体现了安全性,另一方面,不可避免存在一定问题,无论是运行效率还是内存开销,都有待提升。
在当前的多核时代,线程能达到真正的并行,使得多线程并发由此诞生。由于线程是轻量级的进程,线程不独立占用资源,而是依赖于父进程。同一个进程中的多个线程共享同一块资源,线程间能高效地进行数据共享和通信。
信号量sem_t
信号量的数据类型为结构sem_t,本质是一个长整型的数;
初始化方法:extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));
sem为指向信号量结构的一个指针;
pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享;
value为信号量的初始值;
函数sem_post( sem_t *sem )用来增加信号量的值当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不再阻塞,选择机制同样是由线程的调度策略决定的。
函数sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少。
互斥器Mutex
多个线程访问同一资源时,为了保证数据的一致性,最简单的方式就是使用 互斥锁mutex。
总结下Mutex的操作步骤,分为以下几个功能:
1. 定义一个全局互斥器Mutex;
2. 锁住互斥器Mutex:获得一个Mutex的拥有权,其他线程只能等待;
3. 释放互斥器Mutex,使得后一个等待的线程能够拥有它并得以获得资源;
生产者-消费者实例
#ifndef TASK_INTERFACE_H
#define TASK_INTERFACE_H
#include <string>
#include <vector>
using std::string;
using std::vector;
/**
* 多线程任务自定义接口
*/
class TaskInterfece
{
public:
TaskInterfece(){}
virtual void run(vector<string>& dataBuffer) = 0;
};
#endif //TASK_INTERFACE_H
#ifndef MY_TASK_H
#define MY_TASK_H
#include <iostream>
#include "TaskInterfece.h"
using namespace std;
class MyTask : public TaskInterfece
{
public:
MyTask(){}
// do your thing
virtual void run(vector<string>& dataBuffer)
{
cout << "==========\n";
for(int i = 0; i < dataBuffer.size(); ++i)
{
cout << dataBuffer[i] << endl;
}
cout << "**********\n";
}
};
#endif //MY_TASK_H
#ifndef FRAME_H
#define FRAME_H
#include <string>
#include <vector>
#include <utility>
#include <unordered_map>
#include <thread>
#include <mutex>
#include <queue>
#include <condition_variable>
#include <semaphore.h>
#include <iostream>
#include "TaskInterfece.h"
using namespace std;
/**
* 多线程框架
*/
class Frame
{
public:
Frame(){}
bool init(TaskInterfece& task, int t_num, int buf_size = 5000, int log_num = 200000);
void run();
private:
int threadNum;
TaskInterfece* pTask;
mutex bufMtx;
sem_t semPro, semCon;
queue<string> buffer;
vector<thread> threadVec;
int bufSize;
int logNum;
void proThread();
void conThread();
};
#endif //FRAME_H
#include "Frame.h"
bool Frame::init(TaskInterfece& task, int t_num, int buf_size, int log_num)
{
pTask = &task;
threadNum = t_num;
bufSize = buf_size;
logNum = log_num;
sem_init(&semPro, 0, 1);
sem_init(&semCon, 0, 0);
threadVec.clear();
threadVec.push_back(thread(&Frame::proThread, this));
for(int i = 0; i < threadNum; ++i)
{
threadVec.push_back(thread(&Frame::conThread, this));
}
return true;
}
void Frame::run()
{
for(int i = 0; i < threadVec.size(); ++i)
{
threadVec[i].join();
}
}
void Frame::proThread()
{
string line;
int line_num = 0;
int i = 0;
bool finished_flag = false;
while(true)
{
sem_wait(&semPro); // 等待生产者进程
bufMtx.lock();
for(i = 0; i < bufSize; ++i)
{
if(!getline(cin, line)) //数据读入
{
finished_flag = true;
break;
}
line_num++;
buffer.push(line);
if(line_num%logNum == 0)
{
cout << line_num << " lines have finished" << endl;
}
}
bufMtx.unlock();
sem_post(&semCon); // 释放消费者进程
if(finished_flag)
{
break;
}
}
}
void Frame::conThread(){
bool finished_flag = false;
vector<string> input_vec;
input_vec.reserve(bufSize);
while(true)
{
input_vec.clear();
sem_wait(&semCon); // 等待消费者进程
bufMtx.lock();
for(int i = 0; i < bufSize; ++i)
{
if(buffer.empty())
{
finished_flag = true;
break;
}
input_vec.push_back(buffer.front());
buffer.pop();
}
bufMtx.unlock();
sem_post(&semPro); // 释放生产者进程
pTask->run(input_vec); // do my thing
if(finished_flag)
break;
}
sem_post(&semCon);
}
#include "Frame.h"
#include "MyTask.h"
int main()
{
MyTask task;
Frame frame;
frame.init(task, 2, 5, 5); //申请2个线程
frame.run();
return 0;
}