最近重新翻阅了一下c++11新特性,发现标准库中新增了线程类std::thread。实际试用之后,的确比原项目中使用POSIX标准的线程API封装程度更高,用起来也更简单。另外在c++11中还增加了atomic原子变量,简单类型的变量锁也被集成了。或许以后c++多线程开发都可以依赖于标准库进行了,可以省去例如多平台之类的兼容性考虑。
1. thread类构造
线程在thread变量定义时就已经创建并运行了(前提是通过绑定回调函数的方式定义),与之后的join或detach参数并无关系。
线程的启动入口即为构造函数中传递的函数和可变参数列表。函数可以使用全局函数/类静态成员函数,或是类成员函数,但类成员函数需要额外传递对应类的指针,否则编译会报错缺少对应参数。
而可变参数列表则对应着传递函数的参数,如果是类成员函数,那么第一个参数应该是对应类的对象或指针。
构造样例如下所示:
#include <thread>
void func_global() {
printf("callback func:%s\n", __func__);
}
class WorkThread {
public:
WorkThread() {}
void func_memeber() { printf("callback func:%s\n", __func__); }
static void func_static() { printf("callback func:%s\n", __func__); }
};
int main() {
std::thread thrd(func_global); // 全局函数
std::thread thrd1(WorkThread::func_static); // 类静态成员函数
WorkThread wthrd_tmp;
std::thread thrd2(WorkThread::func_memeber, wthrd_tmp); // 类成员函数
// ...
thrd.join();
thrd1.join();
thrd2.join();
return 0;
}
2. thread类操作
与线程类相关的操作主要为join/detach/joinable/move四种,让我们来看看这些函数具体的作用。
join操作:如上图代码样例中所看到的,main函数末尾使用了thread类join函数。其作用为让当前线程等待对应线程工作,一直阻塞到线程工作完成后返回,join()才算执行完成。
detach操作:相比起join操作,detach是将对应线程"流放",不再对其进行控制。需要注意的是,如果创建的子线程中使用了当前线程的变量内容,而当前线程又先于子线程销毁了,那就可能造成无效数据的访问。
joinable操作:该函数的主要作用如同其名字,就是检查线程是否可进行join/detach操作。只有未进行过join/detach操作的线程会返回true,其他则返回false。另外用默认构造函数创建的线程类返回也是false,因为其根本就没有创建过实际线程。
move操作:move函数其实并不是thread类的成员函数,其为c++11新增的右移操作函数。之所以在这里提到,是因为thread类不允许普通的拷贝构造,也不允许相互赋值。该唯一性保证了thread类是可控的,线程的所有权与管理者是明确的,也是线程类能够封装进标准库的前提。因此thread类只支持"转移"操作,我们可以利用move函数右值操作来转移对应的线程所有权。
关于move函数的详细内容大家可以搜索网上介绍,其中涉及了左值右值的概念,此处就暂且不做展开了。
3. thread类析构
只要是变量就会析构,thread类也不例外。但基于线程的特殊性,thread类为用户提供了两个选择:1.结束对应线程。2.分离对应线程。但无论是哪种,都需要用户自己提前决定。因此在thread类发生析构前,需要我们自己调用join或者detach进行处理。
若是不提前进行操作,标准库就会在析构时直接抛出异常,中断程序的运行。这点请务必注意。
4. 代码示例模型
基于thread类与atomic类,博主编写了一个简单的多进程模型。其中将线程创建/运行/销毁封装于WorkThead类内部,由其他类(如MainProcess)进行调用。该模型参考了博主以前项目中服务端进程处理消息的模式,通过多线程消费主进程提供的消息队列,进而提高CPU利用率。
运行后,输入"quit"退出。输入其他命令则会输出对应处理的线程名称。关于退出时调用join的方式可供大家参考,其保证了所有子线程退出后,主线程才会退出。
#include <stdio.h>
#include <thread>
#include <atomic>
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <mutex>
#if defined(__linux__)
#include <unistd.h>
#endif
// 主进程
class WorkThread;
class MainProcess {
public:
friend class WorkThread;
MainProcess(int thread_cnt);
void Start(); // 启动进程,挂载对应数量的线程
void End(); // 结束进程,需要将其下各个线程退出
bool GetPacket(std::string &packet); // 线程安全取消息队列
bool PushPacket(const std::string &packet); // 线程安全存消息队列
private:
int thread_cnt_;
std::atomic<int> value_; // 使用c++11的原子性数据也可以
std::vector<WorkThread*> pthreads_;
std::queue<std::string> packets_; // 或者是自己维护加锁的数据结构
std::mutex lock_;
};
// 工作线程(封装线程类)
class WorkThread {
public:
WorkThread(MainProcess* mainpro, const std::string &thrd_name);
void Start();
void Run();
void End(); // 线程结束时处理数据(如内存回收等)
void StopRun();
private:
bool Dealcmd(const std::string &cmd);
std::thread thd_;
MainProcess *mainpro_;
bool run_;
std::string thread_name_;
};
MainProcess::MainProcess(int thread_cnt) {
thread_cnt_ = thread_cnt;
value_ = 0;
pthreads_.clear();
}
void MainProcess::Start() {
char szname[128];
for (int i = 0; i < thread_cnt_; i++) {
snprintf(szname, sizeof(szname), "thread_%d", i);
WorkThread* pthread = new WorkThread(this, szname);
pthreads_.push_back(pthread);
}
// 线程运行
for (auto &pthread : pthreads_) {
pthread->Start();
}
// 若为服务端进程,此处应为socket接收数据后回调通知
char command[128] = { 0 };
while(1) {
scanf("%s", command);
std::string cmd = command;
if (cmd == "quit") {
break;
} else {
PushPacket(command);
}
}
printf("quit mainprocess\n");
}
void MainProcess::End() {
for (auto &pthread : pthreads_) {
pthread->StopRun();
delete(pthread);
pthread = nullptr;
}
}
// 主线程的加锁消息队列
bool MainProcess::GetPacket(std::string &packet) {
std::lock_guard<std::mutex> lockGuard(lock_);
if (packets_.empty()) {
return false;
}
packet = packets_.front();
packets_.pop();
return true;
}
bool MainProcess::PushPacket(const std::string &packet) {
// 可以加上消息队列大小限制,避免堆包导致内存暴涨不受控制
std::lock_guard<std::mutex> lockGuard(lock_);
packets_.push(packet);
return true;;
}
WorkThread::WorkThread(MainProcess *mainpro, const std::string &thrd_name) {
mainpro_ = mainpro;
thread_name_ = thrd_name;
run_ = false;
}
// 启动线程,并转移给自己的成员变量,确保其生存周期
void WorkThread::Start() {
if (run_ == false) {
run_ = true;
std::thread thrd(&WorkThread::Run, this);
thd_ = std::move(thrd);
} else {
printf("already run.\n");
}
}
// 线程处理函数
void WorkThread::Run() {
std::string cmd;
while(run_) {
if (!mainpro_->GetPacket(cmd)) {
usleep(5000);
} else {
Dealcmd(cmd);
}
}
End();
printf("run end, thread %s exit\n", thread_name_.c_str());
}
bool WorkThread::Dealcmd(const std::string &cmd) {
printf("thread %s deal command : %s\n", thread_name_.c_str(), cmd.c_str());
return true;
}
void WorkThread::End() {
}
// 控制子线程结束运行,同时采用join等待其执行完毕
void WorkThread::StopRun() {
run_ = false;
thd_.join();
printf("thread %s stop run\n", thread_name_.c_str());
}
int main() {
int thread_cnt = 5;
MainProcess mp(thread_cnt);
mp.Start();
mp.End();
printf("main quit\n");
return 0;
}
以后不用记linux那一大串pthread_打头的函数了。
好耶