C++11 thread_local的 用法(二)

简介: C++11 thread_local的 用法(二)

类对象


#include  <iostream>
#include <thread>
#include <mutex>
std::mutex cout_mutex;
//定义类
class A {
public:
    A() {
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "create 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 thread_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;
    }
    return;
}
int main() {
    std::thread t1(thread_func, "t1");
    std::thread t2(thread_func, "t2");
    t1.join();
    t2.join();
    return 0;
}


上面这段代码演示了使用线程局部存储来保证多线程环境下类的独立性。

首先,定义了一个类A,该类具有一个计数器counter和一个成员函数get_value(),用于获取当前计数器的值并将计数器加1。

在主函数中,创建了两个线程t1和t2,分别调用函数thread_func()。函数thread_func()内部定义了一个指向类A对象的指针a,并将其设置为线程局部存储。接着,对a调用get_value()函数,输出当前计数器的值。

由于a是线程局部存储,因此每个线程都拥有自己的a对象。每个线程调用get_value()函数时,都是针对自己的a对象进行操作。这样可以避免多个线程同时操作同一个对象而导致的数据竞争问题。

另外,类A的构造函数和析构函数内部都加了互斥锁,这是为了确保多线程环境下构造和析构操作的安全性。加锁保证了同一时间只有一个线程能够访问类A的构造和析构函数,避免了多个线程同时进行这些操作而引发的竞争问题。

输出:


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


可以看出类对象的使用和创建和内部类型类似,都不会创建多个。这种情况在函数间或通过函数返回实例也是一样的:


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


上面这段代码主要涉及到函数和线程的使用。

函数 A* creatA() 返回指向 A 类对象的指针。在 loopin_func 中,将 creatA() 返回的指针赋值给 thread_local A* a,表示该指针变量的存储期是线程局部的。接着,使用 a 调用 A 类的成员函数 get_value(),并将其返回值输出到控制台上。

thread_func 中,循环调用 loopin_func 函数,每次调用都会生成一个新的 A 类对象并输出其成员变量 counter 的值。

由于 a 是线程局部变量,因此每个线程都会拥有自己的 a 对象,并且每次调用 loopin_func 函数时都会生成一个新的 A 类对象。此外,使用互斥锁保证了输出的线程安全性。

输出:


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


虽然 createA() 看上去被调用了多次,实际上只被调用了一次,因为 thread_local 变量只会在每个线程最开始被调用的时候进行初始化,并且只会被初始化一次

举一反三,如果不是初始化,而是赋值,则情况就不同了:


void loopin_func(const std::string& thread_name) {
    thread_local A* a;
    a = creatA();
    std::lock_guard<std::mutex> lock(cout_mutex);
    std::cout << "thread[" << thread_name << "]: a.counter:" << a->get_value() << std::endl;
    return;
}


上面这段代码定义了一个函数loopin_func,接受一个字符串参数thread_name。该函数在每个线程的本地存储中创建了一个类型为A*的线程局部变量a。然后调用creatA函数创建一个新的A对象,并将指针存储在a中。最后,该函数打印出对象的counter值。

由于每个线程都拥有自己的线程局部变量a,因此creatA在每个线程中只会调用一次,而不是在每次调用loopin_func时都会调用一次。这是因为线程局部变量在第一次使用时会被初始化,并在每个线程的生命周期内保持其值。

输出:


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


很明显,虽然只初始化一次,但却可以被多次赋值,因此 C++ 变量初始化是十分重要的(手动狗头)。


类成员变量


规定:thread_local 作为类成员变量时必须是 static 的


class B {
public:
    B() {
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "create B" << std::endl;
    }
    ~B() {}
    thread_local static int b_key;
    //thread_local int b_key;
    int b_value = 24;
    static int b_static;
};
thread_local int B::b_key = 12;
int B::b_static = 36;
void thread_func(const std::string& thread_name) {
    B b;
    for (int i = 0; i < 3; ++i) {
        b.b_key--;
        b.b_value--;
        b.b_static--;   // not thread safe
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "thread[" << thread_name << "]: b_key:" << b.b_key << ", b_value:" << b.b_value << ", b_static:" << b.b_static << std::endl;
        std::cout << "thread[" << thread_name << "]: B::key:" << B::b_key << ", b_value:" << b.b_value << ", b_static: " << B::b_static << std::endl;
    return;
}


上面这段代码定义了一个名为 B 的类,并在其中定义了三个属性:b_keyb_valueb_static。其中,b_key 是一个线程局部静态变量,而 b_valueb_static 则是普通的类成员变量。这个类的构造函数输出 create B,析构函数为空。

thread_func 函数接收一个字符串参数,表示当前线程的名字。在函数中创建一个 B 类型的对象 b,并循环 3 次。每次循环,将 b_keyb_valueb_static 的值减一,并使用互斥锁锁定输出,将这三个属性的值打印出来,分别加上 thread_nameB::b_keyb_valueB::b_static 的前缀。

输出:


create B
thread[t2]: b_key:11, b_value:23, b_static:35
thread[t2]: B::key:11, b_value:23, b_static: 35
thread[t2]: b_key:10, b_value:22, b_static:34
thread[t2]: B::key:10, b_value:22, b_static: 34
thread[t2]: b_key:9, b_value:21, b_static:33
thread[t2]: B::key:9, b_value:21, b_static: 33
create B
thread[t1]: b_key:11, b_value:23, b_static:32
thread[t1]: B::key:11, b_value:23, b_static: 32
thread[t1]: b_key:10, b_value:22, b_static:31
thread[t1]: B::key:10, b_value:22, b_static: 31
thread[t1]: b_key:9, b_value:21, b_static:30
thread[t1]: B::key:9, b_value:21, b_static: 30


b_key 是 thread_local,虽然其也是 static 的,但是每个线程中有一个,每次线程中的所有调用共享这个变量。b_static 是真正的 static,全局只有一个,所有线程共享这个变量。

目录
相关文章
|
4月前
|
编译器 C++ 容器
【C++】String常见函数用法
【C++】String常见函数用法
|
4月前
|
存储 程序员 编译器
c++学习笔记08 内存分区、new和delete的用法
C++内存管理的学习笔记08,介绍了内存分区的概念,包括代码区、全局区、堆区和栈区,以及如何在堆区使用`new`和`delete`进行内存分配和释放。
49 0
|
5月前
|
C++
C++ string中的函数和常用用法
C++ 中string中的函数和常用用法
44 4
|
6月前
|
存储 C++
C++初阶学习第十一弹——探索STL奥秘(六)——深度刨析list的用法和核心点
C++初阶学习第十一弹——探索STL奥秘(六)——深度刨析list的用法和核心点
55 7
|
6月前
|
存储 人工智能 C++
map容器在C++中的具体用法以及相关注意点
map容器在C++中的具体用法以及相关注意点
54 1
|
7月前
|
编译器 C++
【C++】【C++的常变量取地址问题(对比C的不同)】const修饰的常变量&volatile修饰用法详解(代码演示)
【C++】【C++的常变量取地址问题(对比C的不同)】const修饰的常变量&volatile修饰用法详解(代码演示)
|
7月前
|
安全 程序员 C++
C++ new和delete的用法
需要注意的是,使用 `new`和 `delete`分配和释放内存时,程序员负责管理内存的分配和释放,这可能导致内存泄漏或释放已释放内存的问题。因此,C++引入了智能指针(如 `std::shared_ptr`和 `std::unique_ptr`)以更安全和自动化地管理内存。
107 2
|
7月前
|
存储 C++ 容器
C++标准库容器的基本用法
C++标准库容器的基本用法
55 0
|
7月前
|
Linux Shell 开发工具
C++ 的 ini 配置文件读写/注释库 inicpp 用法 [ header-file-only ]
这是一个C++库,名为inicpp,用于读写带有注释的INI配置文件,仅包含一个hpp头文件,无需编译,支持C++11及以上版本。该库提供简单的接口,使得操作INI文件变得容易。用户可通过`git clone`从GitHub或Gitee获取库,并通过包含`inicpp.hpp`来使用`inicpp::iniReader`类。示例代码展示了读取、写入配置项以及添加注释的功能,还提供了转换为字符串、双精度和整型的函数。项目遵循MIT许可证,示例代码可在Linux环境下编译运行。
474 0
|
7月前
|
C++
C++ 默认参数与引用传递:语法、用法及示例
C++ 允许函数参数具有默认值,简化调用。例如,`void myFunction(string country = &quot;Norway&quot;)` 中`country`默认为&quot;Norway&quot;。默认参数仅适用于函数参数,不包括返回值。引用传递是另一种传递方式,函数直接访问变量内存,允许修改原值,提高效率。`void swapNums(int &x, int &y)` 中`x`和`y`为引用参数。了解这些特性可提升代码可读性和性能。
125 0