C++线程类的封装

操作系统:ubuntu20.04LTS

使用头文件:pthread.h

在之前的文章中已经实现了C++不可复制基类和线程同步类的封装,本文章内容实现对线程的封装。

需求

首先考虑要封装的线程类包含哪些内容,也就是明确类的属性和 方法。需要一个能够在操作系统唯一标识这个线程的id以及线程结构;为了方便追踪线程,可以给线程一个名称;为了控制线程同步,给一个信号量用来阻塞和唤醒线程;还有最重要的线程执行函数。

线程属性

  • 唯一标识线程的id和线程结构
  • 线程的名称
  • 线程的回调函数
  • 信号量

现在考虑需要哪些方法,首先需要构造函数和析构函数,在构造函数中需要制定线程的名称和回调函数,析构函数中需要释放线程所持有的资源;对于线程名称,提供访问和设置的方法;线程常用函数join比必不可少;为了更方便的管理线程,还提供了下面几个函数:1-返回当前正在执行的线程指针;2-设置当前正在执行的线程名词;3-返回当前正在执行的线程名称;4-返回线程的id值。这四个函数为类服务,而不是服务于某个实例,使用static关键字修饰。

那么如何追踪正在执行的线程和现存名词呢?答案是利用C++11提供的关键字thread_local。

thread_local关键字

在C++11中,thread_local是一个关键字,用于声明线程局部存储(TLS)变量。TLS变量是一种特殊类型的变量,每个线程都有其自己的独立实例,每个线程对该变量的访问都是独立的,不会影响其他线程的该变量的值。thread_local关键字可以用于以下三个地方:                

1. 全局变量:如果你在全局范围内声明一个变量,并使用thread_local关键字,那么每个线程都会有该变量的一个独立实例。

2. 静态变量:如果你在一个函数内部或类的成员函数内部声明一个静态变量,并使用thread_local关键字,那么每个线程都会有该静态变量的一个独立实例。

3. 类的非静态成员变量:如果你在类的定义中声明一个非静态成员变量,并使用thread_local关键字,那么每个对象的该成员变量都会有一个独立的实例,而且每个线程都会有属于自己的该成员变量的实例。

使用thread_local关键字可以帮助你在多线程程序中管理数据的共享和隔离,每个线程都有自己的数据副本,避免了数据竞争和线程安全性问题。

举一个例子:下面的类中提供了两个函数,如果创建两个线程分别执行function1或function2函数,那么function1中执行两次后的结果count依然是5,第一个线程执行5次加一后count的值为5,第二个线程依然执行5次加一操作,但是两个线程之间是隔离的,可以看作线程1有一个count,线程2有一个count,这两个分别被执行了5次加一。而function2中static初始化一次,被整个类共享。只有一个count,线程1执行5次加一后线程2对同样的count执行了5次加一,最终得到10.

#include <iostream>
#include <thread>

class Test {
public:
    Test() = default;
    ~Test() = default;
    void function1() {
        thread_local int count = 0;
        for(int i = 0; i < 5; ++i) {
            ++count;
        }
    }
    void function2() {
        static int count = 0;
        for(int i = 0; i < 5; ++i) {
            ++count;
        }
    }
};

线程方法

  • 构造函数
  • 析构函数
  • join函数
  • 设置线程名称函数
  • 返回线程名称函数
  • 返回现存id
  • 设置正在执行线程的名称
  • 返回正在执行线程的名称
  • 将当前线程设置为正在执行的线程
  • 线程内部回调函数

现在我们已经明确了需求,开始实现代码:

#pragma once 

#include <pthread.h>
#include <string.h>
#include <functional>
#include <unistd.h>
#include <thread>
#include <sys/syscall.h>
#include "lock.h"
#include "uncopyable.h"

class Thread : Uncopyable {
public:
    // 构造函数
    Thread(const std::string& name, std::function<void()> callback_func);
    // 析构函数
    ~Thread();
    // 设置线程名称
    void setName(const std::string& name);
    // 返回线程pid
    const pid_t getPID() const;
    // 返回线程名称
    const std::string& getName() const;
    // 等待资源回收
    void join();
    // 设置正在执行线程名称
    static void SetCurrentThreadName(const std::string& name);
    // 获得正在执行的线程名称
    static const std::string& GetCurrentThreadName();
    // 获取正在执行的线程的指针
    static Thread* GetCurrentThreadPtr();
    // 返回线程pid
    static const pid_t GetPID();
    // 返回线程id
    static const std::thread::id GetThreadID();
private:
    // 线程绑定的执行函数
    static void* callbackFunc(void* arg);
private:
    // 进程ID,系统唯一标识
    pid_t pid_;
    // 线程结构,进程中唯一标识线程
    pthread_t pthread_;
    // 线程名
    std::string name_;
    // 线程内部执行函数
    std::function<void()> callback_func_;
    // 信号量,用于阻塞和唤醒线程
    Semaphore semaphore_;
};
#include "thread.h"

static thread_local Thread* current_thread = nullptr;

static thread_local std::string current_thread_name = "UNKNOWN";

// 构造函数
Thread::Thread(const std::string& name, std::function<void()> callback_func):
    name_(name),
    callback_func_(callback_func) {
    if(name.empty()) {
        name_ = "UNKNOWN";
    }
    if(pthread_create(&pthread_, nullptr, &callbackFunc, this)) {
        throw std::runtime_error("pthread_create error");
    }
    semaphore_.wait();
}

// 析构函数
Thread::~Thread() {
    if(pthread_) {
        pthread_detach(pthread_);
    }
}

// 设置线程名称
void Thread::setName(const std::string& name) {
    if(name.empty()) {
        return;
    }
    name_ = name;
}

// 返回线程pid
const pid_t Thread::getPID() const {
    return pid_;
}

// 返回线程名称
const std::string& Thread::getName() const {
    return name_;
}

// 等待资源回收
void Thread::join() {
    if(pthread_join(pthread_, nullptr)) {
        throw std::runtime_error("pthread_join error");
    }
    pthread_ = 0;
}

// 设置正在执行线程名称
void Thread::SetCurrentThreadName(const std::string& name) {
    if(name.empty()) {
        return;
    }
    if(current_thread) {
        current_thread->name_ = name;
    }
    current_thread_name = name;
}
// 设置正在执行的线程名称
const std::string& Thread::GetCurrentThreadName() {
    return current_thread_name;
}

// 获取正在执行的线程的指针
Thread* Thread::GetCurrentThreadPtr() {
    return current_thread;
}   

// 线程绑定的执行函数
void* Thread::callbackFunc(void* arg) {
    Thread* thread = (Thread*)(arg);
    current_thread = thread;
    current_thread_name = thread->name_;
    thread->pid_ = GetPID();   
    pthread_setname_np(pthread_self(), thread->name_.substr(0, 15).c_str());
    std::function<void()> func;
    func.swap(thread->callback_func_);
    thread->semaphore_.post();
    func();
    return 0;
}

// 返回线程id
const pid_t Thread::GetPID() {
    return syscall(SYS_gettid);
}

// 返回线程id
const std::thread::id Thread::GetThreadID() {
    return std::this_thread::get_id();
}

这里简单说明下detach和join,detach并不会阻塞主线程而join主线程需要等的线程执行完毕。

detachjoin
作用将线程与其资源关联解除,使得线程执行结束后自动释放资源等待制定线程执行结束,并可以获取线程的返回值
阻塞行为不会阻塞当前线程,立即返回,无论线程是否执行完毕阻塞当前线程,指导指定的线程执行完毕或超时/阻塞/取消/出错
返回值0表示成功;非0表示失败线程返回值,出错返回错误码
线程回收资源线程结束后资源被自动回收需要主线程显式调用pthread_join函数获取返回值,回收线程资源

在类的析构函数中调用detach函数是为了即使没有调用join函数也可以保证线程所持有的资源被正确释放。

在join函数中,将pthread_设置为0是为了标记线程已经完成,并且资源被释放,避免重复调用join。

还有在callbackFunc中,之所以用swap而不是直接调用,是为了保证执行完后callback_func_变成一个空的函数对象,不再持有任何资源。同时交换函数本身不会抛出异常,因此可以在异常安全的环境中使用,如果交换操作抛出异常,callback_func_中的函数对象不会被修改,保证程序的稳定性。同时若是在多线程下,假设callback_func_是一个可重入的函数对象,swap函数不会改变callback_func_的状态而只是交换了两个对象的内部状态。

最后给出测试函数:

#include "thread.h"
#include <iostream>

void print() {
    std::cout << "Thread " << Thread::GetCurrentThreadName() << " begin " << "pid: " <<
        Thread::GetCurrentThreadPtr()->GetPID() << " | " << Thread::GetCurrentThreadPtr()->getPID() 
        << " thread id: " << Thread::GetCurrentThreadPtr()->GetThreadID() << std::endl;
    Thread::GetCurrentThreadPtr()->setName("线程1");
    std::cout << "change name: " << Thread::GetCurrentThreadPtr()->getName();
    std::this_thread::sleep_for(std::chrono::seconds(2));
    Thread::SetCurrentThreadName("thread test");
    std::cout << "Thread " << Thread::GetCurrentThreadName() << " end" << std::endl;
}

void testThread() {
    Thread thread1("thread1", print);
    Thread thread2("thread2", print);
    thread1.join();
    thread2.join();
}

int main() {
    testThread();
    return 0;
}

  • 15
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值