6.4. 线程本地存储

线程本地存储(TLS)是一个只能由一个线程访问的专门的存储区域。 TLS的变量可以被看作是一个只对某个特定线程而非整个程序可见的全局变量。 下面的例子显示了这些变量的好处。

  1. #include <boost/thread.hpp>
  2. #include <iostream>
  3. #include <cstdlib>
  4. #include <ctime>
  5.  
  6. void init_number_generator()
  7. {
  8. static bool done = false;
  9. if (!done)
  10. {
  11. done = true;
  12. std::srand(static_cast<unsigned int>(std::time(0)));
  13. }
  14. }
  15.  
  16. boost::mutex mutex;
  17.  
  18. void random_number_generator()
  19. {
  20. init_number_generator();
  21. int i = std::rand();
  22. boost::lock_guard<boost::mutex> lock(mutex);
  23. std::cout << i << std::endl;
  24. }
  25.  
  26. int main()
  27. {
  28. boost::thread t[3];
  29.  
  30. for (int i = 0; i < 3; ++i)
  31. t[i] = boost::thread(random_number_generator);
  32.  
  33. for (int i = 0; i < 3; ++i)
  34. t[i].join();
  35. }

该示例创建三个线程,每个线程写一个随机数到标准输出流。 random_number_generator() 函数将会利用在C++标准里定义的 std::rand() 函数创建一个随机数。 但是用于 std::rand() 的随机数产生器必须先用 std::srand() 正确地初始化。 如果没做,程序始终打印同一个随机数。

随机数产生器,通过 std::time() 返回当前时间, 在 initnumber_generator() 函数里完成初始化。 由于这个值每次都不同,可以保证产生器总是用不同的值初始化,从而产生不同的随机数。 因为产生器只要初始化一次, init_number_generator() 用了一个静态变量 _done 作为条件量。

如果程序运行了多次,写入的三分之二的随机数显然就会相同。 事实上这个程序有个缺陷:std::rand() 所用的产生器必须被各个线程初始化。 因此 init_number_generator() 的实现实际上是不对的,因为它只调用了一次 std::srand() 。使用TLS,这一缺陷可以得到纠正。

  1. #include <boost/thread.hpp>
  2. #include <iostream>
  3. #include <cstdlib>
  4. #include <ctime>
  5.  
  6. void init_number_generator()
  7. {
  8. static boost::thread_specific_ptr<bool> tls;
  9. if (!tls.get())
  10. tls.reset(new bool(false));
  11. if (!*tls)
  12. {
  13. *tls = true;
  14. std::srand(static_cast<unsigned int>(std::time(0)));
  15. }
  16. }
  17.  
  18. boost::mutex mutex;
  19.  
  20. void random_number_generator()
  21. {
  22. init_number_generator();
  23. int i = std::rand();
  24. boost::lock_guard<boost::mutex> lock(mutex);
  25. std::cout << i << std::endl;
  26. }
  27.  
  28. int main()
  29. {
  30. boost::thread t[3];
  31.  
  32. for (int i = 0; i < 3; ++i)
  33. t[i] = boost::thread(random_number_generator);
  34.  
  35. for (int i = 0; i < 3; ++i)
  36. t[i].join();
  37. }

用一个TLS变量 tls 代替静态变量 done,是基于用 bool 类型实例化的 boost::threadspecific_ptr 。 原则上, _tls 工作起来就像 done :它可以作为一个条件指明随机数发生器是否被初始化。 但是关键的区别,就是 tls 存储的值只对相应的线程可见和可用。

一旦一个 boost::threadspecific_ptr 型的变量被创建,它可以相应地设置。 不过,它期望得到一个 bool 型变量的地址,而非它本身。使用 reset() 方法,可以把它的地址保存到 _tls 里面。 在给出的例子中,会动态地分配一个 bool 型的变量,由 new 返回它的地址,并保存到 tls 里。 为了避免每次调用 initnumber_generator() 都设置 _tls ,它会通过 get() 函数检查是否已经保存了一个地址。

由于 boost::thread_specific_ptr 保存了一个地址,它的行为就像一个普通的指针。 因此,operator()operator->() 都被被重载以方便使用。 这个例子用 tls 检查这个条件当前是 true 还是 false。 再根据当前的条件,随机数生成器决定是否初始化。

正如所见, boost::thread_specific_ptr 允许为当前进程保存一个对象的地址,然后只允许当前进程获得这个地址。 然而,当一个线程已经成功保存这个地址,其他的线程就会可能就失败。

如果程序正在执行时,它可能会令人感到奇怪:尽管有了TLS的变量,生成的随机数仍然相等。 这是因为,三个线程在同一时间被创建,从而造成随机数生成器在同一时间初始化。 如果该程序执行了几次,随机数就会改变,这就表明生成器初始化正确了。