【linux 多线程并发】线程本地数据存储的两种方式,每个线程可以有同名全局私有数据,以及两种方式的性能分析
线程本地数据(TLS)
专栏内容:
参天引擎内核架构
本专栏一起来聊聊参天引擎内核架构,以及如何实现多机的数据库节点的多读多写,与传统主备,MPP的区别,技术难点的分析,数据元数据同步,多主节点的情况下对故障容灾的支持。手写数据库toadb
本专栏主要介绍如何从零开发,开发的步骤,以及开发过程中的涉及的原理,遇到的问题等,让大家能跟上并且可以一起开发,让每个需要的人成为参与者。
本专栏会定期更新,对应的代码也会定期更新,每个阶段的代码会打上tag,方便阶段学习。
开源贡献:
- toadb开源库
个人主页:我的主页
管理社区:开源数据库
座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.
文章目录
- 线程本地数据(TLS)
- 前言
- 概述
- TLS方式
- TLS生命周期
- 线程pthread结构内存
- __thread 关键字
- 原理介绍
- 代码举例
- 线程API方式
- 创建与销毁接口
- 设置本地变量值接口
- 代码示例
- 两种方式比较
- 总结
- 结尾
前言
现代的CPU都是多core处理器,而且在intel处理器中每个core又可以多个processor,形成了多任务并行处理的硬件架构,在服务器端的处理器上架构又有一些不同,传统的采用SMP,也就是对称的多任务处理架构,每个任务都可以对等的访问所有内存,外设等,而如今在ARM系列CPU上,多采用NUMA架构,它将CPU核分了几个组,给每个组的CPU core分配了对应的内存和外设,CPU访问对应的内存和外设时速度最优,跨组访问时性能会降底一些。
随着硬件技术的持续发展,它们对一般应用的性能优化能力越来越强,同时对于服务器软件的开发,提出更高要求,要想达到极高的并发和性能,就需要充分利用当前硬件架构的特点,对它们进行压榨。那么,我们的应用至少也是要采用多任务架构,不管是多线程还是多进程的多任务架构,才可以充分利用硬件的资源,达到高效的处理能力。
当然多任务框架的采用,不仅仅是多线程的执行,需要对多任务下带来的问题进行处理,如任务执行返回值获取,任务间数据的传递,任务执行次序的协调;当然也不是任务越多处理越快,要避免线程过多导致操作系统夯住,也要防止任务空转过快导致CPU使用率飙高。
本专栏主要介绍使用多线程与多进程模型,如何搭建多任务的应用框架,同时对多任务下的数据通信,数据同步,任务控制,以及CPU core与任务绑定等相关知识的分享,让大家在实际开发中轻松构建自已的多任务程序。
概述
linux 系统中 线程是一种经量级的任务,同一进程的多个线程是共享进程内存的;当我们定义一个全局变量时,它可以被当前进程下的所有线程访问,如何来定义一个线程本地的变量呢?
TLS方式
在linux 系统下一般有两种方式来定义线程本地变量,这一技术叫做Thread Local Storage, TLS。
- GCC的__thread关键字
- 键值对API
TLS生命周期
线程本地变量的生命周期与线程的生命周期一样,当线程结束时,线程本地变量的内存就会被回收。
当然这里需要特别注意,当线程本地变量为指针类型时,动态分配的内存空间,系统并不会自动回收,只是将指针变量置为NULL,为了避免内存泄漏,需要在线程退出时主动进行清理动作,这将在后面的博文中介绍。
线程pthread结构内存
在介绍线程本地变量存储时,就不得不介绍一下pthread结构的内存,它定义了线程的重要数据结构,描述了用户状态线程的完整信息。
pthread 结构非常复杂,通过 specific_1stblock 数组和特定的辅助数组与 TLS 相关。
#define PTHREAD_KEY_2NDLEVEL_SIZE 32
#define PTHREAD_KEY_1STLEVEL_SIZE \((PTHREAD_KEYS_MAX + PTHREAD_KEY_2NDLEVEL_SIZE - 1) \/ PTHREAD_KEY_2NDLEVEL_SIZE)struct pthread
{union{
#if !TLS_DTV_AT_TP/* This overlaps the TCB as used for TLS without threads (see tls.h). */tcbhead_t header;
#elsestruct{int multiple_threads;int gscope_flag;} header;
#endifvoid *__padding[24];};list_t list;pid_t tid;...struct pthread_key_data{/* Sequence number. We use uintptr_t to not require padding on32- and 64-bit machines. On 64-bit machines it helps to avoidwrapping, too. */uintptr_t seq;/* Data pointer. */void *data;} specific_1stblock[PTHREAD_KEY_2NDLEVEL_SIZE];/* Two-level array for the thread-specific data. */struct pthread_key_data *specific[PTHREAD_KEY_1STLEVEL_SIZE];/* Flag which is set when specific data is set. */bool specific_used;...
}
__thread 关键字
该关键字可用于在 GCC/Clang 编译环境中声明 TLS 变量, 该关键字不是 C 标准,并且因编译器不同而有差异;
原理介绍
使用 __thread关键字声明的变量存储在线程的pthred 结构与堆栈空间之间,也就是说,在内存布局方面,从高地址到底层地址的内存分布为:pthred结构、可变区和堆栈区(堆栈的底部和可变区的顶部是连续的);
在这种方式下的线程本地变量,变量的类型不能是复杂的类型,如C++的class类型,而且动态申请的变量空间,需要主动释放,线程结束时,只是对变量空间回收,而对应的动态内存则会泄漏。
代码举例
/* * created by senllang 2024/1/1 * mail : study@senllang.onaliyun.com * Copyright (C) 2023-2024, senllang*/
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>#define THREAD_NAME_LEN 32
__thread char threadName[THREAD_NAME_LEN];
__thread int delay = 0;typedef struct ThreadData
{char name[THREAD_NAME_LEN];int delay;
}ThreadData;void *threadEntry(void *arg)
{int ret = 0;int i = 0;ThreadData * data = (ThreadData *)arg;printf("[%lu] thread entered \n", pthread_self());strncpy(threadName, data->name, THREAD_NAME_LEN);delay = data->delay;for(i = 0; i < delay; i++){usleep(10);}printf("[%lu] %s exiting after delay %d.\n", pthread_self(), threadName, delay);pthread_exit(&ret);
}int main(int argc, char *argv[])
{pthread_t thid1,thid2,thid3;void *ret;ThreadData args1 = {"thread 1", 50000}, args2 = {"thread 2", 25000}, args3 = {"thread 3", 12500};strncpy(threadName, "Main Thread", THREAD_NAME_LEN);if (pthread_create(&thid1, NULL, threadEntry, &args1) != 0) {perror("pthread_create() error");exit(1);}if (pthread_create(&thid2, NULL, threadEntry, &args2) != 0) {perror("pthread_create() error");exit(1);}if (pthread_create(&thid3, NULL, threadEntry, &args3) != 0) {perror("pthread_create() error");exit(1);}if (pthread_join(thid1, &ret) != 0) {perror("pthread_create() error");exit(3);}if (pthread_join(thid2, &ret) != 0) {perror("pthread_create() error");exit(3);}if (pthread_join(thid3, &ret) != 0) {perror("pthread_create() error");exit(3);}printf("[%s]all thread exited delay:%d .\n", threadName, delay);
}
每个线程定义了两个线程本地变量 threadName, delay,在线程处理函数中,对它们赋值后,再延迟一段时间,然后输出这两个变量值,结果可以看到每个线程的本地变量值都不一样,可以独立使用。
运行结果:
[senllang@hatch example_04]$ gcc -lpthread threadLocalStorage_gcc.c
[senllang@hatch example_04]$ ./a.out
[139945977145088] thread entered
[139945960359680] thread entered
[139945968752384] thread entered
[139945960359680] thread 3 exiting after delay 12500.
[139945968752384] thread 2 exiting after delay 25000.
[139945977145088] thread 1 exiting after delay 50000.
[Main Thread]all thread exited delay:0 .
线程API方式
另一种使用线程本地变量的方式,是使用线程key相关的API,它分为两类,一是创建和销毁接口,另一类是变量的设置与获取接口。
这种方式下,线程的本地数据存储在 pthread结构中,其中specific_1stblock,specific两个数组按key值索引,并存储对应的线程本地数据;
线程本地数据的数量,在这种方式下是有限的。
创建与销毁接口
#include <pthread.h>
int pthread_key_create(pthread_key_t *key, void(*destructor)(void*));
int pthread_key_delete(pthread_key_t key);
创建接口,获取一个 pthread_key_t变量的值,其实就是内存获取一个键值来存储数据,第二个参数destructor传递一个销毁数据的方法,当本地数据为复杂数据类型,或者动态申请内存时,在线程退出时进行清理调用。
在线程使用完后,需要释放对应的key。
设置本地变量值接口
#include <pthread.h>
int pthread_setspecific(pthread_key_t key, const void * value);
void * pthread_getspecific(pthread_key_t key);
这里设置线程的本地变量值,和获取线程本地变量值;
在不同线程中设置时,就会只设置当前线程的本地变量,不影响其它线程。
代码示例
/* * created by senllang 2024/1/1 * mail : study@senllang.onaliyun.com * Copyright (C) 2023-2024, senllang*/#include <stdio.h>
#include <pthread.h> // 定义一个 TLS 键
pthread_key_t tls_key; void ShowThreadLocalData(char *prompt, pthread_t thid)
{// 获取 TLS 存储的值 int *value = (int *) pthread_getspecific(tls_key); if (value == NULL) { printf("[%s]Thread: %ld, Value: NULL\n", prompt, thid); } else { printf("[%s]Thread: %ld, Value: %d\n", prompt, thid, *value); }
}// 线程函数
void *thread_func(void *arg)
{ ShowThreadLocalData("pre", pthread_self());pthread_setspecific(tls_key, (void *) arg);ShowThreadLocalData("after", pthread_self());return NULL;
} int main()
{ // 创建 2 个线程 pthread_t thread1, thread2; int args1 = 100, args2=200;pthread_key_create(&tls_key, NULL); // 设置 TLS 值 pthread_setspecific(tls_key, (void *) 500); pthread_create(&thread1, NULL, thread_func, &args1); pthread_create(&thread2, NULL, thread_func, &args2); // 等待线程结束 pthread_join(thread1, NULL); pthread_join(thread2, NULL); pthread_key_delete(tls_key);return 0;
}
在主线程和两个子线程中都设置了本地变量值,运行后,可以看到每个线程中的值都不一样。
[senllang@hatch example_04]$ gcc -lpthread threadLocalStorage_key.c
[senllang@hatch example_04]$ ./a.out
[pre]Thread: 140252914022144, Value: NULL
[after]Thread: 140252914022144, Value: 100
[pre]Thread: 140252905629440, Value: NULL
[after]Thread: 140252905629440, Value: 200
在线程开始时,获取本地变量值,都没有获取到主线程设置的值。
两种方式比较
-
不同的存储区域/寻址方法
API 方式定义的数据由 specific_1stblock 数组和结构的特定辅助数组寻址,而__thread存储类型变量由栈空间地址偏移量寻址。 -
性能/效率差异
由于__thread由栈地址偏移量解决,因此性能高于 API方式。 -
可以存储不同的数据
__thread只能修改常规的POD类型变量,对于指针类型数据,动态申请的内存,需要主动销毁;而 API方式 支持传入销毁方法并支持所有数据类型。 -
支持的数据数量不同
理论上,只要堆栈不满,__thread类型的变量就可以无限期定义;而API 方式只能创建PTHREAD_KEYS_MAX个键,但可以使用一个键通过结构体等方式存储多个值。
总结
本文所涉及的代码已经上传到工程hatchCode, 在multipleThreads/example_04目录下;
线程本地变量的使用,使得线程并发时,与进程并发更加相似,都有自己的私有全局数据,当然线程的特别之处在于,线程的本地变量的空间取决于线程栈的大小,当然也可以是结构指针,再动态申请空间,那么空间也就不存在问题了。
结尾
非常感谢大家的支持,在浏览的同时别忘了留下您宝贵的评论,如果觉得值得鼓励,请点赞,收藏,我会更加努力!
作者邮箱:study@senllang.onaliyun.com
如有错误或者疏漏欢迎指出,互相学习。
相关文章:
【linux 多线程并发】线程本地数据存储的两种方式,每个线程可以有同名全局私有数据,以及两种方式的性能分析
线程本地数据(TLS) 专栏内容: 参天引擎内核架构 本专栏一起来聊聊参天引擎内核架构,以及如何实现多机的数据库节点的多读多写,与传统主备,MPP的区别,技术难点的分析,数据元数据同步,多主节点的…...
2401d,d导入C的问题
原文 D中是否可用仅C头文件库? 在C语言中,我需要这样做: #define STB_DS_IMPLEMENTATION #include "stb_ds.h"在包含h文件前,必须在单个C文件中定义. 在D中试过: enum STB_DS_IMPLEMENTATION 1; import stb_ds;但它不管用.有建议吗?也许使用中间C文件会工作 ,但…...
SpringCloud GateWay实现路由限流
目录 RequestRateLimiterGatewayFilterFactory令牌桶算法实现限流 RequestRateLimiterGatewayFilterFactory Spring Cloud Gateway 内置了一个限流功能的过滤器工厂,那就是RequestRateLimiterGatewayFilterFactory ,它使用 Redis 和 Lua 脚本实现令牌桶…...
打印日期c++
给出年份 y和一年中的第 d天,算出第 d天是几月几号。 输入格式 输入包含多组测试数据。 每组数据占一行,包含两个整数 y 和 d。 输出格式 每组数据输出一行一个结果,格式为 yyyy-mm-dd。 数据范围 输入最多包含 100 组数据, 1≤y≤3000, 1≤d…...
数据结构入门到入土——链表(1)
目录 一,顺序表表/ArrayList的缺陷 二,链表 三,链表的实现 四,与链表有关的题目练习(1) 1.删除链表中等于给定值 val 的所有节点 2.反转一个单链表 3.给定一个带有头结点 head 的非空单链表…...
MySQL C API的使用
MySQL C API的使用 介绍及使用 MySQL C API(也称为 MySQL Connector/C)是用于与 MySQL 数据库交互的 C 语言 API。它提供了一组函数和结构体,允许你在 C 程序中连接到 MySQL 数据库服务器,并执行查询、插入、更新等数据库操作。…...
JavaScript防御性编程
简单聊一下防御性编程,初衷是开发人员为了防止自己被裁员,而将代码编写为只有自己能看懂。如何只有自己能看懂?方法多种多样,但不能将简单问题复杂化,比如:编写一堆无效的逻辑关系,或将业务复杂…...
微信预约小程序制作指南:从小白到专家
在当今的数字时代,微信小程序已经成为了一种非常流行的应用方式。预约功能更是成为了许多小程序的核心功能之一。如果你也想为你的小程序添加预约功能,以下步骤将会对你有所帮助。 一、进入乔拓云网后台 乔拓云网是一个在线小程序开发平台,你…...
向量数据库:Milvus
特性 Milvus由Go(63.4%),Python(17.0%),C(16.6%),Shell(1.3%)等语言开发开发,支持python,go,java接口(C,Rust,c#等语言还在开发中),支持单机、集群部署,支持CPU、GPU运算。Milvus 中的所有搜索和查询操作都在内存中执行…...
亚马逊国际商品详情 API:获取特定商品详细信息的实践
随着电子商务的飞速发展,亚马逊作为全球最大的在线零售商之一,提供了丰富的商品详情 API,使得第三方开发者能够轻松地获取亚马逊网站上的商品信息。本文将介绍如何使用亚马逊国际商品详情 API(Amazon Product Advertising API&…...
MSB30M-ASEMI小贴片整流桥MSB30M
编辑:ll MSB30M-ASEMI小贴片整流桥MSB30M 型号:MSB30M 品牌:ASEMI 封装:UMSB-4 最大平均正向电流:3A 最大重复峰值反向电压:1000V 产品引线数量:4 产品内部芯片个数:4 产品…...
Redis启动方式
redis三种启动方式 1.直接启动 进入redis根目录,执行命令: #加上‘&’号使redis以后台程序方式运行 ./redis-server & 2.通过指定配置文件启动 可以为redis服务启动指定配置文件,例如配置为/etc/redis/6379.conf 进入redis根目录&#x…...
TEMU 新手小白必看!2024入驻流程/入驻类目/入驻资料等详细流程讲解
2023 TEMU 可谓是赚足眼球,流量持续上涨,2024年相信不少卖家们已经跃跃欲试,但大陆卖家如何入驻TEMU?哪些品类适合入驻?又有哪些入驻要求和资料?别急,今天东哥就一一给大家详细讲解,…...
【C语言】数组
一维数组的创建和初始化 数组是一组相同类型元素的集合。 数组的创建 //数组的创建方式:type_t arr_name [const_n];//type_t 是指数组的元素类型//const_n 是一个常量表达式,用来指定数组的大小数组创建的实例: 数组创建ÿ…...
常见测试技术都有哪些?
测试技术是用于评估系统或组件的方法,目的是发现它是否满足给定的要求。系统测试有助于识别缺口、错误,或与实际需求不同的任何类型的缺失需求。测试技术是测试团队根据给定的需求评估已开发软件所使用的最佳实践。这些技术可以确保产品或软件的整体质量…...
Spring事务控制
1.事务介绍 1.1什么是事务? 当你需要一次执行多条SQL语句时,可以使用事务。通俗一点说,如果这几条SQL语句全部执行成功,则才对数据库进行一次更新,如果有一条SQL语句执行失败,则这几条SQL语句全部不进行执…...
swaggerUI不好用,试试这个openapiUI?
title: swaggerUI不好用,试试这个openapiUI? date: 2024-01-08 categories: [tool] tags: [openapi,工具] description: 基于swaggger2, openapi3规范的UI文档 1.背景 由于长期使用 swaggerUI 工具,它的轻量风格个人觉得还是不错的,但是它…...
嵌入式物联网项目开发实战例程-STM32F103系列之外围器件代码
开发STM32F103很好的参考例程,轻松实现各类外围器件的开发。持续更新中,欢迎关注及收藏。 0001基于STM32F103单片机GPIO实现控制LED灯闪烁的程序代码.zip 0002基于STM32F103单片机GPIO实现按键KEY的检测程序代码.zip 0003基于STM32F103单片机GPIO实现外部…...
Docker Compose--部署SpringBoot项目--实战
原文网址:Docker Compose--部署SpringBoot项目--实战-CSDN博客 简介 本文用实战介绍Docker Compose部署SpringBoot项目。 ----------------------------------------------------------------------------------------------- 分享Java真实高频面试题,…...
单电阻FOC算法实现永磁同步电机的调整步骤和设置
本文档介绍了使用 单电阻FOC 算法实现永磁同步电机(Permanent Magnet Synchronous Motor,PMSM)调整所需的步骤和设置。由于不同电机存在参数差异,因此需针对不同的电机和负载对该算法进行调整。该电机库已经在在落地扇和空净等风机…...
化学DS-1040 Tosylate 抑制剂 1335138-89-0科研用途
化合物1219962-49-8是一种小分子化合物,分子式为C15H25N3O4,相对分子质量为305.37。该化合物为白色至灰白色粉末,不溶于水,易溶于有机溶剂,如甲醇、乙醇等。 AT791是一种与细胞周期调控相关的蛋白激酶,参与…...
PaddlePaddle初使用
模型导出与预测 # -c 后面设置训练算法的yml配置文件 # -o 配置可选参数 # Global.pretrained_model 参数设置待转换的训练模型地址,不用添加文件后缀 .pdmodel,.pdopt或.pdparams。 # Global.save_inference_dir参数设置转换的模型将保存的地址。pytho…...
【FPGA】分享一些FPGA数字信号处理相关的书籍
在做FPGA工程师的这些年,买过好多书,也看过好多书,分享一下。 后续会慢慢的补充书评。 【FPGA】分享一些FPGA入门学习的书籍【FPGA】分享一些FPGA协同MATLAB开发的书籍 【FPGA】分享一些FPGA视频图像处理相关的书籍 【FPGA】分享一些FPGA高速…...
深度解析JavaScript面试热点:事件循环、上下文、箭头函数、变量作用域与ES6模块
JavaScript面试中经常涉及到事件循环、上下文、箭头函数、变量作用域以及ES6模块等核心概念。通过清晰的代码示例,我们深入讨论这些主题,揭示其中的关键细节。 事件循环(Event Loop) JavaScript开发者每天都与事件循环打交道&am…...
Javaweb之Mybatis的动态SQL的详细解析
3. Mybatis动态SQL 3.1 什么是动态SQL 在页面原型中,列表上方的条件是动态的,是可以不传递的,也可以只传递其中的1个或者2个或者全部。 而在我们刚才编写的SQL语句中,我们会看到,我们将三个条件直接写死了。 如果页面…...
物联网与智能家居:跨境电商与未来生活的融合
物联网(Internet of Things,IoT)和智能家居技术正迅速崛起,成为跨境电商领域的创新引擎。这两者的巧妙结合不仅为消费者提供更智能、便捷的生活方式,同时也为电商平台和制造商带来了全新的商机。本文将深入探讨物联网与…...
Java内存模型(JMM)是基于多线程的吗
Java内存模型(JMM)是基于多线程的吗 这个问题按我的思路转换了下,其实就是在问:为什么需要Java内存模型 总结起来可以由几个角度来看待「可见性」、「有序性」和「原子性」 面试官:今天想跟你聊聊Java内存模型&#…...
Linux离线安装MySQL(rpm)
目录 下载安装包安装MySQL检测安装结果服务启停MySQL用户设置 下载安装包 下载地址:https://downloads.mysql.com/archives/community/ 下载全量包如:(mysql-8.1.0-1.el7.x86_64.rpm-bundle.tar) 解压:tar -xzvf mysql-8.1.0-1.el7.x86_64.…...
用 Socket.D 替代原生 WebSocket 做前端开发
socket.d.js 是基于 websocket 包装的 socket.d 协议的实现。就是用 ws 传输数据,但功能更强大。 功能原生 websocketsocket.d说明listen有有监听消息send有有发消息sendAndRequest无有发消息并接收一个响应(类似于 http)sendAndSubscribe无…...
Transformer架构和对照代码详解
1、英文架构图 下面图中展示了Transformer的英文架构,英文架构中的模块名称和具体代码一一对应,方便大家对照代码、理解和使用。 2、编码器 2.1 编码器介绍 从宏观⻆度来看,Transformer的编码器是由多个相同的层叠加⽽ 成的,每个…...
公司网站开发 flask/百度竞价推广计划
Iperf 是一个网络性能测试工具1、server 192.168.224.30client 192.168.224.20tar xf iperf-3.1.2.tar.gzcd iperf-3.1.2./configure --prefix/usr/local/iperfmake&&make install2、serveriperf -s -D-s 以server模式启动-D 后台守护进程运行3、clientiperf -c 192.16…...
seo文章关键词怎么优化/上海seo优化公司 kinglink
Django4.0 -----借鉴:cls超:点击这里 语法 语法 {{ 变量 }} {% 逻辑 %} 变量的使用和说明 在Django的模板语言中按此语法使用:{{ 变量名 }}。 当模版引擎遇到一个变量,它将计算这个变量,然后用结果替换掉它本身。 …...
新疆建设厅招投标网站/sem 优化价格
原文:http://coolketang.com/staticPhotoshop/5a98d5fd0b61607bf6cc2ebe.html 1. 本节课程将为您演示,如何全选图片,和取消对图片的选择。依次点击[选择 > 全部]命令,全部选择示例图片。 2. 3. 然后点击键盘上的快捷键…...
哪里有做装修网站/小红书关键词热度查询
使用Numpy内部功能函数 Numpy具有用于创建数组的内置函数。 创建一个一维的数组 arrange是一种广泛使用的函数,用于快速创建数组。 >>> import numpy as np >>> arraynp.arange(20) >>> array array([ 0, 1, 2, 3, 4, 5, 6, …...
个人blog网站/友情链接交易
1.使用python创建一个类 以创建一个Dog的类为例: class Dog():def __init__(self, name, age):self.name nameself.age agedef sit(self):print(self.name.title() "is now sitting.")def roll_over(self):print(self.name rolled over!)创建了一个…...
用python 做网站/seo网站免费优化软件
首先引入类库,Microsoft.Office.Interop.Word,然后进行编程。代码如下:首先引入类库,Microsoft.Office.Interop.Word,然后进行编程。代码如下: using System; using System.Collections.Generic; using System.ComponentModel; us…...