首页 > 编程语言 >[C++11与并发编程]7、本地变量线程安全

[C++11与并发编程]7、本地变量线程安全

时间:2022-12-05 17:35:48浏览次数:59  
标签:11 std thread C++ 线程 call local once

本地变量线程安全


layout: post title: 本地变量线程安全 categories: cpp_concurrency description: C++并发编程简介 keywords: c++, 并发编程,本地变量线程安全


  • ​本地变量线程安全​
  • ​​keywords: c++, 并发编程,本地变量线程安全​​
  • ​​非线程安全函数​​
  • ​​用户自己提供buf​​
  • ​​线程中实例化各自buf​​
  • ​​static变量的线程安全​​
  • ​​call\_once 和 once\_flag​​
  • ​​thread\_local关键字​​
  • ​​使用thread\_local封装线程​​

非线程安全函数

在早期的clib中存在很多非线程安全的函数。

比如:

#include <string.h>
char *strerror(int errnum);

这个版本的strerror使用一个静态的char *来存放错误信息,如果在多线程的情况下则可能出现不可预知的情况。要解决这个问题有几种方法:

  • 提供额外的函数,由调用者提供buf,而不是使用库的静态变量的buf
  • 在每个线程中实例化各自的buf,比如使用 __thread、thread_local或者pthread_getspecific
  • 返回局部的非静态变量

用户自己提供buf

在新版本的函数库中,比如​​timelocal​​为了线程安全就是需要调用者自己提供buf的,新旧接口如下:

#include <time.h> // #include <ctime>

struct tm* __cdecl localtime(__time64_t const* _Time);
static __inline errno_t localtime_s(struct tm*const _Tm,time_t const* const _Time);

localtime返回的是一个静态或全局唯一的struct tm指针,因此是非线程安全的。而localtime_s返回struct tm的buf是由调用者自己提供的,因此是线程安全的。

线程中实例化各自buf

如果为了方便函数调用,不希望调用者自己去提供buf,简化编程,可以由库的编写者将变量声明为线程安全的,可以使用下面3个修饰符:

  • __thread
  • thread_local
  • pthread_getspecific

要使用他们需要引入pthread.h头文件,并且pthread_getspecific是最慢的,不建议使用。

Recent GCC, e.g. GCC 5 do support C11 and its thread_local (if compiling with e.g. gcc -std=c11). As FUZxxl commented, you could use (instead of C11 thread_local) the __thread qualifier supported by older GCC versions. Read about Thread Local Storage. pthread_getspecific is indeed quite slow (it is in the POSIX library, so is not provided by GCC but e.g. by GNU glibc or musl-libc) since it involves a function call. Using thread_local variables will very probably be faster. Look into the source code of MUSL's thread/pthread_getspecific.c file for an example of implementation. Read this answer to a related question. And _thread & thread_local are (often) not magically translated to calls to pthread_getspecific. They usually involve some specific address mode and/or register (details are implementation specific, related to the ABI; on Linux, I guess that since x86-64 has more registers & address modes, its implementation of TLS is faster than on i386), with help from the compiler, the linker and the runtime system. It could happen on the contrary that some implementations of pthread_getspecific are using some internal thread_local variables (in your implementation of POSIX threads). As an example, compiling the following code

#include <pthread.h>

const extern pthread_key_t key;

__thread int data;

int
get_data (void) {
return data;
}

int
get_by_key (void) {
return *(int*) (pthread_getspecific (key));
}
//using GCC 5.2 (on Debian/Sid) with gcc -m32 -S -O2 -fverbose-asm gives the following code for get_data using TLS:

// .type get_data, @function
// get_data:
// .LFB3:
// .cfi_startproc
// movl %gs:data@ntpoff, %eax # data,
// ret
// .cfi_endproc
// and the following code of get_by_key with an explicit call to pthread_getspecific:

// get_by_key:
// .LFB4:
// .cfi_startproc
// subl $24, %esp #,
// .cfi_def_cfa_offset 28
// pushl key # key
// .cfi_def_cfa_offset 32
// call pthread_getspecific #
// movl (%eax), %eax # MEM[(int *)_4], MEM[(int *)_4]
// addl $28, %esp #,
// .cfi_def_cfa_offset 4
// ret
// .cfi_endproc

Hence using TLS with __thread (or thread_local in C11) should probably be faster than using pthread_getspecific (avoiding the overhead of a call). Notice that thread_local is a convenience macro defined in <threads.h> (a C11 standard header).

在C++11之前,如果一个线程初始化某一资源后要在其他线程中使用该资源需要由程序员自己保证资源的初始化在使用之前,另外一种情况是某一资源初始化代码可能会被多次执行也需要程序员自己去保证代码的同步。在C++11中引入了static初始化安全保证以及call_once函数保证资源只被初始化一次。

static变量的线程安全

C++11标准保证局部的static变量初始化在多线程环境中的安全。

call_once 和 once_flag

C++标准库提供了std::once_flag和std::call_once,它一般应用于多线程中的条件初始化。比起锁住互斥量,并显式的检查指针,每个线程只需要使用std::call_once,在std::call_once的结束时,它保证相关资源已经被安全的初始化完成了。使用std::call_once比显式使用互斥量消耗的资源更少,特别是当初始化完成后。

call_once调用后为啥资源就是已经线程安全的初始化了呢??实际上就是cpp保证了std::call_once的调用和执行是线程安全的,至于你要在该函数里面做什么,那是你自己的事。如果你没有在里面初始化相关的资源,那么程序肯定是不能按照你的预期执行的。

std::shared_ptr<some_resource> resource_ptr;
std::once_flag resource_flag; // 1

void init_resource()
{
resource_ptr.reset(new some_resource);
}

void foo()
{
std::call_once(resource_flag,init_resource); // 可以完整的进行一次初始化
resource_ptr->do_something();
}

thread_local关键字

thread_local关键字用于定义线程本地变量。当一个变量被定义为thread_local的时候,系统会在每个运行的线程中都实例化出一个该变量。它可以很方便的在多线程环境中获取正在运行的线程中相关的信息。比如标准库给我们提供的​​std::this_thread​​​实际上就是一个​​thread_local​​​的​​std::thread​​​对象,我们可以使用​​std::this_thread​​在任意的地方、任意的线程对当前正在运行的线程进行操作。比如让出当前线程的时间片:

std::this_thread::yeld();

使用thread_local封装线程

标签:11,std,thread,C++,线程,call,local,once
From: https://blog.51cto.com/u_6650004/5913103

相关文章

  • 线程池
    Executor框架集图ExecutorServicejuc.ExecutorService继承自Executor接口ThreadPoolExecutor成员变量:ctl://表示两种含义://①:workerCount:worker计数,表示正在......
  • [C++11与并发编程]5、使用条件变量和互斥锁实现信号量
    使用条件变量和互斥锁实现信号量layout:posttitle:使用条件变量和互斥锁实现信号量categories:cpp_concurrencydescription:C++并发编程简介keywords:c++,并发编......
  • [C++11与并发编程]条件变量在生产者-消费者模型中的使用
    条件变量在生产者-消费者模型中的使用layout:posttitle:条件变量在生产者-消费者模型中的陷阱categories:cpp_concurrencydescription:C++并发编程简介keywords:c+......
  • C++读写二进制文件
    方法一:#include<stdio.h>#include<stdlib.h>#include<fstream>#include<string>//size_treadBinaryFile(constchar*filename,unsignedchar*&data){......
  • C++接口工程实践
    https://zhuanlan.zhihu.com/p/213902091还没有学习完简介:程序开发的时候经常会使用到接口。众所周知,C++语言层面并没有接口的概念,但并不意味着C++不能实现接口的功能。......
  • VMware Fusion 13虚拟机如何安装win 11教程
    VM虚拟机如何安装win11?还不知道如何在VMwareFusion13虚拟机中安装win11的朋友,下面就和小编一起来了解一下!VMwareFusion13虚拟机1、运行VMwareFusion13虚拟机,在......
  • 升级到win11 22h2的体验
    win1122h2更稳定了在win1122h2发布后没多久,我就升级到了这个版本,截止目前已经使用半个月了,谈谈我的使用感受。总体要比之前的版本更稳定,表现为笔记本风扇不会突然响,突然卡......
  • 问题:fatal: unable to access 'https://github.com/XXXXX': GnuTLS recv error (-110)
    问题:fatal:unabletoaccess'https://github.com/XXXXX':GnuTLSrecverror(-110):TheTLSconnectionwasnon-properlyterminated.我遇到了这个问题,感觉关了ssl验......
  • 在c#中调用c++的dll崩溃了,try catch 怎么获取异常?
    在framework框架下,通过添加HandleProcessCorruptedStateExceptionsAttribute属性来解决这个问题,(.netcore1.0到3.1之前,不支持从损坏的进程状态异常中恢复,即trycatch没有......
  • C++ IMPL模式解析(下)
    二进制兼容在上一章结尾处提到了二进制兼容的概念,这里先说说二进制兼容的问题。为什么是二进制兼容简单说,就是我的可执行程序调用你的动态库(so/dll),若动态库发生改动,我......