C++多线程:thread_local

2023-11-10

概念

首先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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++多线程:thread_local 的相关文章

  • 访问私人成员[关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 通过将类的私有成员转换为 void 指针 然后转换为结构来访问类的私有成员是否合适 我认为我无权修改包含我需要访问的数据成员的类 如果不道德 我
  • C# 和 Javascript SHA256 哈希的代码示例

    我有一个在服务器端运行的 C 算法 它对 Base64 编码的字符串进行哈希处理 byte salt Convert FromBase64String serverSalt Step 1 SHA256Managed sha256 new S
  • Qt-Qlist 检查包含自定义类

    有没有办法覆盖加载自定义类的 Qt QList 的比较机制 即在 java 中你只需要重写一个比较方法 我有一个带有我的自定义类模型的 QList QList
  • 将数组向左或向右旋转一定数量的位置,复杂度为 o(n)

    我想编写一个程序 根据用户的输入 正 gt 负 include
  • 未解决的包含:“cocos2d.h” - Cocos2dx

    当我在 Eclipse 中导入 cocos2dx android 项目时 我的头文件上收到此警告 Unresolved inclusion cocos2d h 为什么是这样 它实际上困扰着我 该项目可以正确编译并运行 但我希望这种情况消失
  • C++ 子字符串返回错误结果

    我有这个字符串 std string date 20121020 我正在做 std cout lt lt Date lt lt date lt lt n std cout lt lt Year lt lt date substr 0 4 l
  • 指针问题(仅在发布版本中)

    不确定如何描述这一点 但我在这里 由于某种原因 当尝试创建我的游戏的发布版本进行测试时 它的敌人创建方面不起作用 Enemies e level1 3 e level1 0 Enemies sdlLib 500 2 3 128 250 32
  • 在 Visual Studio 2008 上设置预调试事件

    我想在 Visual Studio 中开始调试程序之前运行一个任务 我每次调试程序时都需要运行此任务 因此构建后事件还不够好 我查看了设置的 调试 选项卡 但没有这样的选项 有什么办法可以做到这一点吗 你唯一可以尝试的 IMO 就是尝试Co
  • 如何将图像和 POST 数据上传到 Azure 移动服务 ApiController 终结点?

    我正在尝试上传图片and POST表单数据 尽管理想情况下我希望它是json 到我的端点Azure 移动服务应用 我有ApiController method HttpPost Route api upload databaseId sea
  • C 预处理器库

    我的任务是开发源分析工具C程序 并且我需要在分析本身之前预处理代码 我想知道什么是最好的图书馆 我需要一些重量轻 便于携带的东西 与其推出自己的 为什么不使用cpp这是的一部分gcc suite http gcc gnu org onlin
  • 在 ASP.NET Core 3.1 中使用包含“System.Web.HttpContext”的旧项目

    我们有一些用 Net Framework编写的遗留项目 应该由由ASP NET Core3 1编写的API项目使用 问题是这些遗留项目正在使用 System Web HttpContext 您知道它不再存在于 net core 中 现在我们
  • 在数据库中搜索时忽略空文本框

    此代码能够搜索数据并将其加载到DataGridView基于搜索表单文本框中提供的值 如果我将任何文本框留空 则不会有搜索结果 因为 SQL 查询是用 AND 组合的 如何在搜索 从 SQL 查询或 C 代码 时忽略空文本框 private
  • 从路径中获取文件夹名称

    我有一些路c server folderName1 another name something another folder 我如何从那里提取最后一个文件夹名称 我尝试了几件事 但没有成功 我只是不想寻找最后的 然后就去休息了 Thank
  • 将自定义元数据添加到 jpeg 文件

    我正在开发一个图像处理项目 C 我需要在处理完成后将自定义元数据写入 jpeg 文件 我怎样才能做到这一点 有没有可用的图书馆可以做到这一点 如果您正在谈论 EXIF 元数据 您可能需要查看exiv2 http www exiv2 org
  • clang 实例化后静态成员初始化

    这样的代码可以用 GCC 编译 但 clang 3 5 失败 include
  • 实体框架 4 DB 优先依赖注入?

    我更喜欢创建自己的数据库 设置索引 唯一约束等 使用 edmx 实体框架设计器 从数据库生成域模型是轻而易举的事 现在我有兴趣使用依赖注入来设置一些存储库 我查看了 StackOverflow 上的一些文章和帖子 似乎重点关注代码优先方法
  • WCF:将随机数添加到 UsernameToken

    我正在尝试连接到用 Java 编写的 Web 服务 但有些东西我无法弄清楚 使用 WCF 和 customBinding 几乎一切似乎都很好 除了 SOAP 消息的一部分 因为它缺少 Nonce 和 Created 部分节点 显然我错过了一
  • 为什么 C# Math.Ceiling 向下舍入?

    我今天过得很艰难 但有些事情不太对劲 在我的 C 代码中 我有这样的内容 Math Ceiling decimal this TotalRecordCount this PageSize Where int TotalRecordCount
  • 为什么我收到“找不到编译动态表达式所需的一种或多种类型。”?

    我有一个已更新的项目 NET 3 5 MVC v2 到 NET 4 0 MVC v3 当我尝试使用或设置时编译出现错误 ViewBag Title财产 找不到编译动态表达式所需的一种或多种类型 您是否缺少对 Microsoft CSharp
  • Process.Start 阻塞

    我正在调用 Process Start 但它会阻止当前线程 pInfo new ProcessStartInfo C Windows notepad exe Start process mProcess new Process mProce

随机推荐

  • 静态联编与动态联编

    联编是指一个程序模块 代码之间相互关联的过程 静态联编 是程序的匹配 链接在编译阶段实现 也称早期匹配 重载函数就使用静态联编 编译的阶段 动态联编是指程序联编推迟到运行时候进行 又称晚期匹配 switch if语句就是动态联编的例子 执行
  • JVM调优几款好用的内存分析工具

    对于高并发访问量的电商 物联网 金融 社交等系统来说 JVM内存优化是非常有必要的 可以提高系统的吞吐量和性能 通常调优的首选方式是减少FGC次数或者FGC时间 以避免系统过多地暂停 FGC达到理想值后 比如一天或者两天触发一次FGC FC
  • (转载)jquery checkbox 设置选中和不选中

    https blog csdn net hantanxin article details 103187996 1 设置选中 hasApply prop checked true 设置不选中 hasApply prop checked fa
  • 使用openCV比对任意两张图片的相似度(亲测较准确)

    方案 使用openCV中的直方图算法做对比 测试效果较好 步骤 在java中使用openCV 1 引入openCV的依赖
  • openwrt 没有wifi

    wifi radio0 is disabled radio0 is disabled uci set wireless radio0 disabled 0
  • Web中什么是token,token的组成部分详解(jwt Token)

    token是计算机术语 令牌 令牌是一种能够控制站点占有媒体的特殊帧 以区别数据帧及其他控制帧 token其实说的更通俗点可以叫暗号 在一些数据传输之前 要先进行暗号的核对 不同的暗号被授权不同的数据操作 使用基于 Token 的身份验证方
  • K8s基础9——服务发现Coredns、Ingress Controller多种暴露方式、TLS+DaemonSet、Headless Services

    文章目录 一 服务发现机制 1 1 环境变量注入 1 2 DNS解析 二 Ingress 4 1 部署Ingress controller 4 2 暴露Ingress Controller 4 2 1 SVC NodePort方式 4 2
  • ElementUI 之el-form表单重置功能按钮

    业务场景 使用el form时 点击重置按钮或者取消按钮时会实现表单重置效果 重置功能按钮功能实现详细步骤 第一 首先给el form添加ref属性
  • 手把手教你做出数据可视化项目(四)动态模拟航班飞行路线

    数据可视化前言 https blog csdn net diviner s article details 115933789 项目最终效果图 此篇博客为自己学习pink老师的课后完成的项目的总结与记录 仅供交流参考 版权所有 转载请标注原
  • 服务器2016系统怎么添加用户名,windows-server-2016 – 如何在Nano Server中添加SMTP服务器角色?...

    使用 this TechNet page上的信息 我已经成功建立了一个远程PowerShell会话 其中包含在Hyper V VM中运行的2016 Preview 2 Nano Server 我现在想要添加SMTP服务器角色 我期待这是一个
  • android 史上最简单的下拉选择菜单DropDownMenu 几行代码轻松搞定!

    这是我在CSDN上第一篇原创文章 趁着从上家公司离职去考驾照的这段日子 想通过写技术博客的方式 锻炼一下自己的语言表达能力 以及对之前工作的总结 废话不多说了 直接进入正题 先给客官来张效果图 一 思路 下拉菜单首先让我想到了PopupWi
  • Java代码审计详解

    一 Fortify代码审计工具 1 Fortify简介 Fortify是Micro Focus旗下AST 应用程序安全测试 产品 其产品组合包括 Fortify Static Code Analyzer提供静态代码分析器 SAST Fort
  • 每日一道Leetcode——按奇偶排序数组II

    题目 我的解法一 双端队列 思路 用两个双端队列分别存储奇数和偶数 然后依次取一个 class Solution public int sortArrayByParityII int A Deque
  • 拓世AI

    2023年的小红书 发展趋势依旧昂扬向上 最新数据显示 小红书拥有逾3亿的月活用户 且超过80 的用户集中在20 30岁年龄段 这代表什么 广大的年轻用户基数和消费能力 正处于购买力上升期的年轻人 是品牌最想抓住的目标用户 巨大的红利吸引了
  • Visual C# 2010 实现菜单项和状态栏

    演练 向窗体提供标准菜单项 Visual Studio 2010 其他版本 此主题尚未评级 评价此主题 可以通过 MenuStrip 控件为窗体提供标准菜单 此演练演示如何使用 MenuStrip 控件创建标准菜单 窗体还将在用户选择菜单项
  • karatsuba大数乘法问题及其高效算法

    转载自 iTimeTraveler博客 题目 编写两个任意位数的大数相乘的程序 给出计算结果 比如 题目描述 输出两个不超过100位的大整数的乘积 输入 输入两个大整数 如1234567 和 123 输出 输出乘积 如 151851741
  • arcgis应用程序无法正常启动0xc0000906

    第一 在开始 运行里输入CMD确定 在命令行窗口下输入以下内容后按回车for 1 in windir system32 ocx do regsvr32 s 1完了后 再输入以下内容并回车 第二 再输入以下内容并回车for 1 in wind
  • Android 代码混淆语法讲解及常用模板,app架构图

    keepclassmembers class R public static 表示不混淆 R 类中 的 static 变量 在 R 类中 这些资源 ID 是系统自动帮我们生成的 混淆了就无法找到相应的资源 dontwarn android
  • VUE 之 项目常规配置详解

    Vue 项目的常规配置可以分为以下几个方面 路由配置 使用 Vue Router 进行路由配置 需要在 src router index js 文件中配置路由表和路由守卫 状态管理 使用 Vuex 进行状态管理 需要在 src store
  • C++多线程:thread_local

    概念 首先thread local是一个关键词 thread local是C 11新引入的一种存储期指定符 它会影响变量的存储周期 Storage duration 与它同是存储期指定符的还有以下几个 关键字 说明 备注 auto 自动存储