c++11 std::thread多线程简单模型

最近重新翻阅了一下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_打头的函数了。

好耶

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值