C++多线程:thread_local

概念

首先thread_local是一个关键词,thread_local是C++ 11新引入的一种存储期指定符。它会影响变量的存储周期(Storage duration),与它同是存储期指定符的还有以下几个:

关键字说明备注
auto自动存储期c++11前, “auto int x; ” 在c++11起错误
register自动存储期。指示编译器将此对象置于处理器的寄存器中。c++17弃用
static静态或者线程存储期的内部链接
extern静态或者线程存储期的外部链接
thread_local线程存储期c++11起
mutable不影响存储期或链接

thread_local指示对象拥有线程存储期。也就是对象的存储在线程开始时分配,而在线程结束时解分配。每个线程拥有其自身的对象实例。唯有声明为 thread_local 的对象拥有此存储期。 thread_local 能与 static 或 extern 结合一同出现,以调整链接(分别指定内部或外部链接),详细的可以查阅:存储类说明符 - cppreference.com

thread_local 关键词只对声明于命名空间作用域的对象、声明于块作用域的对象及静态数据成员允许。举例如下:

thread_local int x;  // 1 A thread-local variable at namespace scope
class X
{
    static thread_local std::string s; // 2 A thread-local static class data member
};
static thread_local std::string X::s;  //The definition of X::s is required

void foo()
{
    thread_local std::vector<int> v;  // 3 A thread-local local variable
}

下面我们来具体学习一下thread_local的这几种应用场景。

全局变量

#include <iostream>
#include <thread>
#include <mutex>

std::mutex cout_mutex;    // 用于多线程打印
thread_local int x = 1;

void func(const std::string& thread_name) {
    for (int i = 0; i < 3; ++i) {
        x++;
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "thread[" << thread_name << "]: x = " << x << std::endl;
    }
}

int main() {
    std::thread t1(func, "t1");
    std::thread t2(func, "t2");
    t1.join();
    t2.join();
    return 0;
}

输出:

thread[t1]: x = 2
thread[t1]: x = 3
thread[t1]: x = 4
thread[t2]: x = 2
thread[t2]: x = 3
thread[t2]: x = 4

从上输出也确实能看出,每个线程都有自己单独的x副本,互不干预。

局部变量

#include <iostream>
#include <thread>
#include <mutex>
std::mutex cout_mutex;    //方便多线程打印

void func(const std::string& thread_name) {
    for (int i = 0; i < 3; ++i) {
        thread_local int x = 1;
        x++;
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "thread[" << thread_name << "]: x = " << x << std::endl;
    }
}

int main() {
    std::thread t1(func, "t1");
    std::thread t2(func, "t2");
    t1.join();
    t2.join();
    return 0;
}

输出:

thread[t1]: x = 2
thread[t1]: x = 3
thread[t1]: x = 4
thread[t2]: x = 2
thread[t2]: x = 3
thread[t2]: x = 4

thread_local的局部变量没有因为for循环作用域而重新赋值。这是因为线程存储期的变量都是和线程绑定的,所以只有第一次声明时被赋值。可以理解为线程专用的static变量。不过变量的作用域依然是在本身的作用域内。比如:在for循环外使用x就会编译时错误。

类对象

#include <iostream>
#include <thread>
#include <mutex>
std::mutex cout_mutex;

class A {
public:
    A() {
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "initialize A" << std::endl;
    }
    ~A() {
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "destroy A" << std::endl;
    }

    int counter = 0;
    int get_value() {
        return counter++;
    }
};

void func(const std::string& thread_name) {
    for (int i = 0; i < 3; ++i) {
        thread_local A* a = new A();
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "thread[" << thread_name
            << "]: a.counter:" << a->get_value() << std::endl;
    }
}

int main() {
    std::thread t1(func, "t1");
    std::thread t2(func, "t2");
    t1.join();
    t2.join();
    return 0;
}

输出:

initialize A
thread[t1]: a.counter:0
thread[t1]: a.counter:1
thread[t1]: a.counter:2
initialize A
thread[t2]: a.counter:0
thread[t2]: a.counter:1
thread[t2]: a.counter:2

可以看出虽然在循环中创建了A的实例a,但是并没有因为循环创建了多个。这个与局部变量的情况相同,创建的实例相对于thread是static的。

但是如果没有在声明时进行赋值,就不一样了。如:

#include <iostream>
#include <thread>
#include <mutex>
std::mutex cout_mutex;

class A {
public:
    A() {
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "initialize A" << std::endl;
    }

    ~A() {
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "destroy A" << std::endl;
    }

    int counter = 0;
    int get_value() {
        return counter++;
    }
};

void func(const std::string& thread_name) {
    for (int i = 0; i < 3; i++) {
        thread_local A* a;
        a = new A();
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "thread[" << thread_name << "]: a.counter:" << a->get_value() << std::endl;
    }
    return;
}

int main() {
    std::thread t1(func, "t1");
    std::thread t2(func, "t2");
    t1.join();
    t2.join();
    return 0;
}

输出:

initialize A
thread[t1]: a.counter:0
initialize A
thread[t1]: a.counter:0
initialize A
thread[t1]: a.counter:0
initialize A
thread[t2]: a.counter:0
initialize A
thread[t2]: a.counter:0
initialize A
thread[t2]: a.counter:0

很好理解,在循环执行时,每次都创建了一个A实例并对a进行赋值。所有一般情况要求我们:thread_local对象声明时赋值

类成员变量

thread_local作为类成员变量时必须是static的,修改代码:

#include <iostream>
#include <thread>
#include <mutex>
std::mutex cout_mutex;

class A {
public:
    A() {
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "initialize A" << std::endl;
    }
    ~A() {
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "destroy A" << std::endl;
    }

    thread_local static int key_;
    int value_ = 24;
    static int static_;
};
int A::static_ = 36;
thread_local int A::key_ = 12;


void func(const std::string& thread_name) {
    A aa;
    for (int i = 0; i < 3; ++i) {
        aa.key_--;
        aa.value_--;
        aa.static_--;
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "thread[" << thread_name << "]: key_:" << aa.key_
            << ", value_:" << aa.value_ << ", static_:" << aa.static_ << std::endl;
        std::cout << "thread[" << thread_name << "]: A::key_:" << A::key_
            << ", value_:" << aa.value_ << ", static_: " << A::static_ << std::endl;
    }
    return;
}

int main() {
    std::thread t1(func, "t1");
    std::thread t2(func, "t2");
    t1.join();
    t2.join();
    return 0;
}

输出:

initialize A
thread[t1]: key_:11, value_:23, static_:35
thread[t1]: A::key_:11, value_:23, static_: 35
thread[t1]: key_:10, value_:22, static_:34
thread[t1]: A::key_:10, value_:22, static_: 34
thread[t1]: key_:9, value_:21, static_:33
thread[t1]: A::key_:9, value_:21, static_: 33
destroy A
initialize A
thread[t2]: key_:11, value_:23, static_:32
thread[t2]: A::key_:11, value_:23, static_: 32
thread[t2]: key_:10, value_:22, static_:31
thread[t2]: A::key_:10, value_:22, static_: 31
thread[t2]: key_:9, value_:21, static_:30
thread[t2]: A::key_:9, value_:21, static_: 30
destroy A

从上面例子可以看出thread_local作为类成员时也是对于每个thread分别分配了一个。而static则是全局一个。

其他

本质上thread_local修饰后仍然是一个变量,我们依旧能够使用取地址操作者通过引用的方法传递给其他线程对其进行修改:

#include <iostream>
#include <thread>

thread_local int i=0;

void func(int* p){
    *p = 42;
}

int main(){
    i = 9;
    std::thread t(func, &i);
    t.join();
    std::cout << i << std::endl;
}

程序将输出42

另外,thread_local 变量在第一次使用时初始化,如果变量(类)没有被使用。此变量(类)将不会被初始化:

#include <iostream>
#include <thread>

struct A {
    A() {
        std::cout<< "initialized A" << std::endl;
    }
    ~A() {
        std::cout << "deleted A" << std::endl;
    }
    int i;
};

thread_local my_class ss;

void do_nothing() {
}

int main(){
    std::thread t1(do_nothing);
    t1.join();
}

总结

thread-local storage 和 static(或者说global) 存储很类似,每一个线程都将拥有一份这个数据的拷贝,thread_local对象的生命周期从线程开始时开始(对于全局变量),或者首先分配空间。当线程退出的时候对象析构;

一般在声明时赋值,在本thread中只执行一次。当用于类成员变量时,必须是static的。

参考:

C++11 thread_local用法 - 知乎 (zhihu.com)

存储类说明符 - cppreference.com

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值