本地变量线程安全
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
- 参考-stackoverflow
如果为了方便函数调用,不希望调用者自己去提供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封装线程
- 使用thread_local封装线程