线程-本地数据

线程-本地数据(也称为线程-本地存储)是为每个线程单独创建的,其行为类似于静态数据。在命名空间范围内,或作为静态类成员的线程局部变量,是在第一次使用之前创建,而在函数中声明的线程局部变量是在第一次使用时创建,并且线程-本地数据只属于线程。

  1. // threadLocal.cpp
  2. #include <iostream>
  3. #include <string>
  4. #include <mutex>
  5. #include <thread>
  6. std::mutex coutMutex;
  7. thread_local std::string s("hello from ");
  8. void addThreadLocal(std::string const& s2) {
  9. s += s2;
  10. // protect std::cout
  11. std::lock_guard<std::mutex> guard(coutMutex);
  12. std::cout << s << std::endl;
  13. std::cout << "&s: " << &s << std::endl;
  14. std::cout << std::endl;
  15. }
  16. int main() {
  17. std::cout << std::endl;
  18. std::thread t1(addThreadLocal, "t1");
  19. std::thread t2(addThreadLocal, "t2");
  20. std::thread t3(addThreadLocal, "t3");
  21. std::thread t4(addThreadLocal, "t4");
  22. t1.join();
  23. t2.join();
  24. t3.join();
  25. t4.join();
  26. }

通过在第10行中使用关键字thread_local,可以创建线程本地字符串s。线程t1 - t4(第27 - 30行)使用addThreadLocal函数(第12 - 21行)作为工作包。线程分别获取字符串t1t4作为参数,并添加到线程本地字符串s中。另外,addThreadLocal在第18行会打印s的地址。

线程-本地数据 - 图1

程序的输出在第17行显示内容,在第18行显示地址。要为字符串s创建线程本地字符串:首先,每个输出显示新的线程本地字符串;其次,每个字符串都有不同的地址。

我经常在研讨会上讨论:静态变量、thread_local变量和局部变量之间的区别是什么?静态变量与主线程的生命周期相同,thread_local变量与其所在线程的生存周期相同,而局部变量与创建作用域的生存周期相同。为了说明我的观点,来看一下代码。

  1. // threadLocalState.cpp
  2. #include <iostream>
  3. #include <string>
  4. #include <mutex>
  5. #include <thread>
  6. std::mutex coutMutex;
  7. thread_local std::string s("hello from ");
  8. void first() {
  9. s += "first ";
  10. }
  11. void second() {
  12. s += "second ";
  13. }
  14. void third() {
  15. s += "third";
  16. }
  17. void addThreadLocal(std::string const& s2) {
  18. s += s2;
  19. first();
  20. second();
  21. third();
  22. // protect std::cout
  23. std::lock_guard<std::mutex> guard(coutMutex);
  24. std::cout << s << std::endl;
  25. std::cout << "&s: " << &s << std::endl;
  26. std::cout << std::endl;
  27. }
  28. int main() {
  29. std::cout << std::endl;
  30. std::thread t1(addThreadLocal, "t1: ");
  31. std::thread t2(addThreadLocal, "t2: ");
  32. std::thread t3(addThreadLocal, "t3: ");
  33. std::thread t4(addThreadLocal, "t4: ");
  34. t1.join();
  35. t2.join();
  36. t3.join();
  37. t4.join();
  38. }

代码中,函数addThreadLocal(第24行)先调用函数first ,然后调用second,再调用third 。每个函数都使用thread_local字符串s来添加它的函数名。这种变化的关键之处在于,字符串s在函数firstsecondthird中操作时,处于一种本地数据的状态(第28 - 30行),并且从输出表明字符串是独立存在的。

线程-本地数据 - 图2

单线程到多线程

线程本地数据有助于将单线程程序移植成多线程程序。如果全局变量是线程局部的,则可以保证每个线程都得到其数据的副本,从而避免数据竞争。

与线程-本地数据相比,条件变量的使用门槛更高。