Bootstrap

C++语法|thread_local详解

文章内容全部来自:
【C++入门到进阶 多线程 thread_local 关键字】
【CPU眼里的:thread_local】

简介

thread_local 是一个关键字,它用来修饰变量,并被他修饰的变量有以下特征:

  • 它指示对象拥有线程静态存储期
    • 线程存储期:对象的存储在线程开始时分配,而在线程结束时析构。每个线程拥有其自身的对象实例。 thread_local 能与 static 和 extern 一同出现,以调整链接。
  • 它具有 static 修饰的变量一样的初始化特征和生命周期
    • 也就是说它的初始化只进行一次
  • 需要注意的是他在单独修饰类成员变量时,必须加上 static

测试线程存储周期和类static特性

我们构建这样的测试代码:

class A
{
public:
    A() {
        cout << __FUNCTION__ << endl;
    }
    ~A() {
        cout << __FUNCTION__ << endl;
    }

    int v = 0;
    void add () {
        //static int v = 0;
        cout << std::this_thread::get_id() << " "
            << ++v << endl;
        cout.flush();
	}
};

thread_local A a;

int main () {
    a;
    return 0;
}

对于我们在main()函数外面初始化类A,我们可以看到只构造了一个A类。

随后我们更改测试代码:

thread_local A a;

void threadFunc() {
    a.add();
}

int main () {
    thread t1(threadFunc);
    thread t2(threadFunc);
    t1.join();
    t2.join();

    a.add();

    return 0;
}

可以看到结果我们一共构造了3个A实例,并且都完成析构。

这就是我们的线程静态存储期。

测试 修饰类成员变量加上static

如果你在修饰成员变量的时候,不加static会直接报错。

class A
{
public:
    A() {
        cout << __FUNCTION__ << endl;
    }
    ~A() {
        cout << __FUNCTION__ << endl;
    }

    thread_local inline static int v = 0;
    void add () {
        //static int v = 0;
        cout << std::this_thread::get_id() << " "
            << ++v << endl;
        cout.flush();
	}
};

随后我们来验证该类成员变量 v 的线程存储周期:
我们创建了两个子线程,然后再子线程中调用 add() ,也就是让 v自增,并且我们在主线程中也调用 add() ,让 v 自增。按照前面的理论我们应该能得出:三个v的打印必须全部是1:

A a;

void threadFunc() {
    // 每个线程第一次访问 thread_local 变量 a 时,都会调用构造函数
    a.add();
}
int main () {
    thread t1(threadFunc);
    thread t2(threadFunc);
    t1.join();
    t2.join();

    a.add();

    return 0;
}
//结果如下
// A
// 140127114585664 1
// 140127122978368 1
// 140127122982720 1
// ~A

我们可以看到,A变量全局只有一个被存储在 .data段,被各个线程共享。然后我们开始让v完成自增操作,我们可以看到v打印出来都是1,说明该类的成员变量 v 每个线程都有且仅有一个。

;