6.4-6.5 C++11线程局部存储与快速退出:quick_exit与at_quick_exit

一、线程局部存储

线程局部存储(TLS,thread local storage),指的是线程局部存储变量,即拥有线程生命期及线程可见性的变量。

常见的全局变量和静态变量对于多线程而言是共享的,我们可以通过这些实现线程间数据传递与交互,但是有时我们并不希望这些变量被多线程共享,比如errno。

#include 

#include 
#include 
using namespace std;
int errorCode = 0;

void MaySetErr(void * input) {
	if (*(int*)input == 1)
		errorCode = 1;
	else if (*(int*)input == 2)
		errorCode = -1;
	else
		errorCode = 0;
	int i = 10;
	while (i>0)
	{
		i--;
		cout<

上面的例子中使用全局的errorCode作为错误码,这样方便保存线程中的错误码,但是对于多线程二线,可能存在错误码覆盖的问题,比如thread1的错误码1和thread2的错误码-1可能会相互覆盖。而解决的办法就是为每个线程指派一个全局的errorCode,即TLS化的errorCode。

C++11对TLS标准做出了一些统一的规定。与__thread修饰符类似,声明一个TLS变量的语法很简单,即通过thread_local修饰符声明变量即可。

int thread_local errCode;

实际上TLS还有一些其他的应用场景:

  • 线程特定的数据,比如线程id,状态信息等。
  • 避免全局变量竞争, 全局变量在多线程环境中可能会引发数据竞争和同步问题,而TLS使得每个线程都有自己的变量副本,从而避免这些问题
  • 性能优化:在一些情况下,使用 TLS 可以提高性能。例如,如果一个函数需要一个大的缓冲区,而这个函数在多个线程中都被频繁调用,那么每次调用都分配和释放缓冲区可能会影响性能。你可以使用 TLS 来为每个线程分配一个缓冲区,然后在多次调用之间重用这个缓冲区。
  • 以及上面提到的错误处理。

关于TLS还有个讨论比较多的就是初始化问题,C++11标准允许平台/编译器自行选择采用静态分配或动态分配,或者两者都支持。此外,C++11对TLS只是做了语法上的统一,而对其实现并没有做任何性能上的规定,因此TLS在不同平台上的性能需要参考平台说明文档。

二、快速退出:quick_exit与at_quick_exit

C++中存在正常退出和异常退出两种情况,其中异常退出主要是对于发生异常而未被捕获时产生,此时会调用terminate,而terminate默认行为是调用abort函数终止程序,当然也可以通过set_terminate修改默认行为。

对于abort而言,不会调用任何析构函数,而是抛出一个信号终止程序,这可能会导致与之交互的其他进程进入交互“中间态”,而产生一些问题。

而exit则是正常退出,会正常调用自动变量的析构函数,以及atexit注册的函数(反顺序调用)。

当然exit来自与C,在C++中类可能零散的分布于堆上,而调用exit则需要依次调用这些类的析构函数释放内存,这无疑是耗时的。而实际上,这些堆内存在进程结束时由操作系统统一回收会很快。因此在这种场景下我们往往需要更快速的退出。

因此,在C++11中,标准引入了quick_exit函数,该函数并不执行析构函数而只是使程序终止。与abort不同的是,abort的结果通常是异常退出(可能系统还会进行coredump等以辅助程序员进行问题分析),而quick_exit与exit同属于正常退出。此外,使用at_quick_exit注册的函数也可以在quick_exit的时候被调用。这样一来,我们同样可以像exit一样做一些清理的工作(这与很多平台上使用_exit函数直接正常退出还是有不同的)。在C++11标准中,at_quick_exit和at_exit一样,标准要求编译器至少支持32个注册函数的调用。

你可能感兴趣的:(深入理解C++11新特性,C++11)