多线程局部存储技术

问题

多线程上下文中,每个线程需要使用一个专属的全局变量,该如何实现?

代码示例

一种可能的解决方案

test1.c

#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */
#define _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <semaphore.h>

#define g_value (global->value)
#define g_fl    (global->fl)
#define g_str   (global->str)

#define func1(i, j)      func1_(global, i, j)
#define func2(s)         func2_(global, s)
#define more_func_call() more_func_call_(global)

typedef struct
{
    int value;
    float fl;
    char* str;
} ThreadGlobal;

void func1_(ThreadGlobal* global, int i, int j)
{
    g_fl = 1.0 * i / j;
}

void func2_(ThreadGlobal* global, char* s)
{
    g_str = s;
}

void more_func_call_(ThreadGlobal* global)
{
    printf("g_value = %u\n", g_value);
    printf("g_fl = %f\n", g_fl);
    printf("g_str = %s\n", g_str);
}

void* child_thread(void* arg)
{   
    ThreadGlobal* global = arg;
    
    /* global variable initialization */
    g_value = pthread_self();
    g_fl = 0;
    g_str = "Delphi Tang";
    
    /* function call */
    func1(1, rand() % 10);
    func2("Test");
    
    more_func_call();
       
    return NULL;
}

int main()
{
    pthread_t t = {0};
    int i = 0;
    
    for(i=0; i<5; i++)
    {
        pthread_create(&t, 
                       NULL, 
                       child_thread, 
                       malloc(sizeof(ThreadGlobal)));
    }

    sleep(3);
  
    return 0;
}

ThreadGlobal 为我们定义的包含多种基本数据类型的结构体

第 67 行,在创建子线程的时候,我们会 malloc 一个 ThreadGlobal 这个结构体,将这个指针传入每个子线程,这样每个子线程都有唯一的变量

但在后续的子线程在函数调用的时候,我们需要将指向 ThreadGlobal 类型的指针作为函数的参数传入来区分每个线程专属的全局变量,这边我们使用了宏定义,优化了这个问题

程序运行结果如下图所示:

方案缺陷分析

NPTL 解决方案

test2.c

#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */
#define _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <semaphore.h>

static pthread_key_t g_thread_global;

void func_1()
{
    int value = (long long)pthread_getspecific(g_thread_global);
    
    printf("value = %d\n", value);
}

void* thread_1(void* arg)
{    
    // set global to 255
    pthread_setspecific(g_thread_global, (void*)255);
    
    func_1(); // call to access local global

    // avoid free error
    pthread_setspecific(g_thread_global, 0);
       
    return NULL;
}


void func_2()
{
    char* s = pthread_getspecific(g_thread_global);
    
    printf("s = %s\n", s);
}

void* thread_2(void* arg)
{   
    char* pc = malloc(16);
    
    // set thread local global
    strcpy(pc, "Delphi Tang"); 
    
    printf("pc = %p\n", pc);
    
    pthread_setspecific(g_thread_global, pc);
    
    func_2(); // call to access local global
       
    return NULL;
}

void thread_delete(void* key)
{
    printf("key = %p\n", key);
    
    free(key);
}

int main()
{
    pthread_t t1 = {0};
    pthread_t t2 = {0};
    
    pthread_key_create(&g_thread_global, thread_delete);

    pthread_create(&t1, NULL, thread_1, NULL);
    pthread_create(&t2, NULL, thread_2, NULL);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    
    pthread_key_delete(g_thread_global);
  
    return 0;
}

程序运行结果如下图所示:

NPTL 方案缺陷分析

NPTL 线程局部存储相关函数太难用,并且使用其编写的代码可读性差!

NPTL 方案的原理剖析

深度思考

NPTL 方案的编码细节是否可自动完成 (编译器自动完成)

传统 C 语言全局变量是否可拓展出多份拷贝 (每个线程一个拷贝)

如何区分传统的全局变量和拓展的全局变量?

什么时候使用拓展的全局变量?

GCC 解决方案

__thread int g_global = 0;

被 __thread 修饰的变量,每个线程都会有该变量的一份拷贝

如果使用了 static 或 extern 关键字,那么 __thread 位于其后

__thread 变量可在声明时进行初始化,也可使用 & 操作符获取地址值

GCC 解决方案示例

多线程局部存储技术

test3.c

#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */
#define _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <semaphore.h>

static __thread long long g_global;

void func_1()
{
    printf("v = %lld\n", g_global);
}

void* thread_1(void* arg)
{   
    g_global = 255;  // set thread local global
    
    printf("thread_1 : &g_global = %p\n", &g_global);
    
    func_1(); // call to access local global
       
    return NULL;
}


void func_2()
{
    printf("s = %s\n", (char*)g_global);
}

void* thread_2(void* arg)
{   
    g_global = (long long)malloc(16);
    
    // set thread local global
    strcpy((char*)g_global, "Delphi Tang"); 
    
    printf("thread_2 : &g_global = %p\n", &g_global);

    func_2(); // call to access local global
       
    return NULL;
}

/*
void thread_delete(void* key)
{
    printf("key = %p\n", key);
    
    free(key);
}
*/

int main()
{
    pthread_t t1 = {0};
    pthread_t t2 = {0};
    
    // pthread_key_create(&g_thread_global, thread_delete);

    pthread_create(&t1, NULL, thread_1, NULL);
    pthread_create(&t2, NULL, thread_2, NULL);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    
    // pthread_key_delete(g_thread_global);
  
    return 0;
}

第 10 行,我们通过 __thread 关键字来修饰变量 g_global,使得 g_global 变量在每个线程都有一份拷贝

在 thread_1 线程中,我们将 g_global 赋值为一个整形;在 thread_2 线程中,我们将 g_global 赋值为一个指向字符串的指针;在两个线程中打印 g_global 的地址和值

程序运行结果如下图所示:

在两个线程中,g_global 的地址并不相同,值也不同;说明 __thread 关键字使得 g_global 变量在每个线程都有一份拷贝

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/593385.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

谷歌上架,为什么会触发填表单,可以避免吗?怎么填表单可以提高通过率?

在谷歌上架过程中&#xff0c;相信大部分开发者都有收到过谷歌发来表单填写的邮件通知&#xff0c;要求开发者们在14天内根据表单要求回复关于应用部分情况。邮件如图&#xff1a; 根据触发填表单的开发者分享的经验来看&#xff0c;填完表之后出现的情况不尽相同&#xff0c;且…

【华为】路由综合实验(OSPF+BGP基础)

【华为】路由综合实验 实验需求拓扑配置AR1AR2AR3AR4AR5PC1PC2 查看通信OSPF邻居OSPF路由表 BGPBGP邻居BGP 路由表 配置文档 实验需求 ① 自行规划IP地址 ② 在区域1里面 启用OSPF ③ 在区域1和区域2 启用BGP&#xff0c;使AR4和AR3成为eBGP&#xff0c;AR4和AR5成为iBGP对等体…

【JVM】class文件格式,JVM加载class文件流程,JVM运行时内存区域,对象分配内存流程

这篇文章本来只是想讲一下class文件格式&#xff0c;讲着讲着越讲越多。JVM这一块吧&#xff0c;知识比较散比较多&#xff0c;如果深研究下去如死扣《深入理解Java虚拟机》&#xff0c;这本书很深很细&#xff0c;全记住是不可能的&#xff0c;其实也没必要。趁这个机会直接把…

RK3568平台(基础篇)linux错误码

一.概述 linux应用程序开发过程中&#xff0c;经常会遇到一些错误信息的返回&#xff0c;存在的可能性有&#xff0c;参数有误、非法访问、系统资源限制、设备/文件不存在、访问权限限制等等。对于这类错误&#xff0c;可以通过perror函数输出具体描述&#xff0c;或者通过str…

nacos-server-1.2.1启动

1、双击startup.cmd 2、启动日志 3、访问http://192.168.26.210:8848/nacos/index.html 4、登录 用户名&#xff1a;nacos 密码&#xff1a;nacos

掌握JavaScript面向对象编程核心密码:深入解析JavaScript面向对象机制对象概念、原型模式与继承策略全面指南,高效创建高质量、可维护代码

ECMAScript&#xff08;简称ES&#xff0c;是JavaScript的标准规范&#xff09;支持面向对象编程&#xff0c;通过构造函数模拟类&#xff0c;原型链实现继承&#xff0c;以及ES6引入的class语法糖简化面向对象开发。对象可通过构造函数创建&#xff0c;使用原型链共享方法和属…

【云原生】Docker 的网络通信

Docker 的网络通信 1.Docker 容器网络通信的基本原理1.1 查看 Docker 容器网络1.2 宿主机与 Docker 容器建立网络通信的过程 2.使用命令查看 Docker 的网络配置信息3.Docker 的 4 种网络通信模式3.1 bridge 模式3.2 host 模式3.3 container 模式3.4 none 模式 4.容器间的通信4.…

小白如何搭建git

1、安装git 在Windows上安装git&#xff1a; 关注微信公众号“机器人学”回复 “搭建git” 利用百度云网盘下载安装包&#xff0c;建议下载如下版本的否则可能会出现错误。 安装完成后&#xff0c;在开始菜单里Git->git bash&#xff0c;弹出命令窗说明git安装成功。 鼠标右…

【Python项目】基于DJANGO的【基于语音识别的智能垃圾分类系统】

技术简介&#xff1a;使用Python技术、DJANGO框架、MYSQL数据库等实现。 系统简介&#xff1a;用户们可以在系统上面录入自己的个人信息&#xff0c;录入后还可以对信息进行修改&#xff0c;网站可以对用户上传的音频文件进行识别&#xff0c;然后进行垃圾分类。 背景&#xf…

【学习AI-相关路程-工具使用-自我学习-NVIDIA-cuda-工具安装 (1)】

【学习AI-相关路程-工具使用-自我学习-NVIDIA-cuda &#xff08;1&#xff09;】 1、前言2、环境配置1、对于jetson orin nx 的cuda环境2、对于Ubuntu 20.04下cuda环境 3、自我总结-安装流程1、在ubuntu下&#xff0c;如果想使用cuda平台&#xff0c;应该注意什么 和 都安装什么…

BGE向量模型架构和训练细节

模型论文&#xff1a;https://arxiv.org/pdf/2309.07597 模型数据&#xff1a;https://data.baai.ac.cn/details/BAAI-MTP 训练数据 由无标签数据和有标签数据组成。 无标签数据使用了悟道等数据集&#xff0c;有标签数据使用了dureader等数据集。 都是文本对&#xff0c;对于…

rust使用Atomic创建全局变量和使用

Mutex用起来简单&#xff0c;但是无法并发读&#xff0c;RwLock可以并发读&#xff0c;但是使用场景较为受限且性能不够&#xff0c;那么有没有一种全能性选手呢&#xff1f; 欢迎我们的Atomic闪亮登场。 从 Rust1.34 版本后&#xff0c;就正式支持原子类型。原子指的是一系列…

【redis】Redis数据类型(五)ZSet类型

目录 类型介绍特点补充 使用场景 Zset类型数据结构ziplist&#xff1a;压缩列表&#xff08;参考之前的文章&#xff09;skiplist&#xff1a;跳表解析 面试题&#xff1a;MySQL索引为什么用B树而不用跳表区别总结 常用命令ZADD示例 ZREM示例 ZCARD示例 ZCOUNT示例 ZSCORE示例 …

在线OJ——链表经典例题详解

引言&#xff1a;本篇博客详细讲解了关于链表的三个经典例题&#xff0c;分别是&#xff1a;环形链表&#xff08;简单&#xff09;&#xff0c;环形链表Ⅱ&#xff08;中等&#xff09;&#xff0c;随机链表的复制&#xff08;中等&#xff09;。当你能毫无压力地听懂和成功地…

单调栈|496.下一个更大元素I

力扣题目链接 class Solution { public:vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {stack<int> st;vector<int> result(nums1.size(), -1);if (nums1.size() 0) return result;unordered_map<int, …

去哪儿网机票服务请求体bella值逆向

作者声明&#xff1a;文章仅供学习交流与参考&#xff01;严禁用于任何商业与非法用途&#xff01;否则由此产生的一切后果均与作者无关&#xff01;如有侵权&#xff0c;请联系作者本人进行删除&#xff01; 一、加密定位 直接全局搜索bella&#xff0c;在可疑的地方下断&…

pandas学习笔记12

缺失数据处理 其实在很多时候&#xff0c;人们往往不愿意过多透露自己的信息。假如您正在对用户的产品体验做调查&#xff0c;在这个过程中您会发现&#xff0c;一些用户很乐意分享自己使用产品的体验&#xff0c;但他是不愿意透露自己的姓名和联系方式&#xff1b; 还有一些用…

使用C#和NMODBUS快速搭建MODBUS从站模拟器

MODBUS是使用广泛的协议&#xff0c;通讯测试时进行有使用。Modbus通讯分为主站和从站&#xff0c;使用RS485通讯时同一个网络内只能有一个主站&#xff0c;多个从站。使用TCP通讯时没有这方面的限制&#xff0c;可以同时支持多个主站的通讯读写。 开发测试时有各种复杂的需求&…

05 - 步骤 JSON output

简介 JSON Output 步骤用于将 Kettle 中的行流数据写出到 JSON 格式的文件或流中。它允许用户将 Kettle 中处理过的数据以 JSON 格式进行输出&#xff0c;适用于各种数据处理和交换场景。 什么是行流数据&#xff1f; preview data 中的每一个字段都是一个行流数据 使用 场…

54.HarmonyOS鸿蒙系统 App(ArkTS)tcp socket套接字网络连接收发测试

工程代码https://download.csdn.net/download/txwtech/89258409?spm1001.2014.3001.5501 54.HarmonyOS鸿蒙系统 App(ArkTS)tcp socket套接字网络连接收发测试 import socket from ohos.net.socket; import process from ohos.process; import wifiManager from ohos.wifiMana…