前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >Boost C++ 库 | 多线程

Boost C++ 库 | 多线程

原创
作者头像
Qt历险记
发布2024-10-24 20:52:48
发布2024-10-24 20:52:48
13400
代码可运行
举报
文章被收录于专栏:C++C++
运行总次数:0
代码可运行

点击上方"蓝字"关注我们

01、序言

>>> 线程就是,在同一程序同一时间内允许执行不同函数的离散处理队列。这使得一个长时间去进行某种特殊运算的函数在执行时不阻碍其他的函数变得十分重要。线程实际上允许同时执行两种函数,而这两个函数不必相互等待。 一旦一个应用程序启动,它仅包含一个默认线程。此线程执行 main() 函数。在 main()中被调用的函数则按这个线程的上下文顺序地执行。这样的程序称为单线程程序。 反之,那些创建新的线程的程序就是多线程程序。他们不仅可以在同一时间执行多个函数,而且这在如今多核盛行的时代显得尤为重要。既然多核允许同时执行多个函数,这就使得对开发人员相应地使用这种处理能力提出了要求。然而线程一直被用来当并发地执行多个函数,开发人员现在不得不仔细地构建应用来支持这种并发。多线程编程知识也因此在多核系统时代变得越来越重要。 本章将介绍C++ Boost库 Boost.Thread,它可以开发独立于平台的多线程应用程序。

代码语言:javascript
代码运行次数:0
复制
#include <boost/thread.hpp> // 引入 Boost 线程库#include <iostream> // 引入输入输出库​// 定义一个等待函数,接收一个整型参数 sec,表示等待的秒数void wait(int sec) {    // 调用 Boost 库中的 sleep 函数,使当前线程休眠指定的秒数    boost::this_thread::sleep(boost::posix_time::seconds(sec));}​// 定义线程执行的函数void thread() {    // 循环 5 次    for(int i = 0; i < 5 ; ++ i) {        // 每次循环等待 1 秒        wait(1);        // 输出当前的循环计数        std::cout << i << std::endl;    }}​int main(){    // 创建一个新的线程 t,并指定执行 thread 函数    boost::thread t(thread);    // 等待线程 t 执行完毕    t.join();}​// 输出 0 1 2 3 4

>>>新建线程里执行的那个函数的名称被传递到 boost::thread 的构造函数。一旦上述示例中的变量 t 被创建,该 thread() 函数就在其所在线程中被立即执行。同时在 main() 里也并发地执行该 thread() 。 为了防止程序终止,就需要对新建线程调用 join() 方法。 join() 方法是一个阻塞调用:它可以暂停当前线程,直到调用 join() 的线程运行结束。这就使得 main() 函数一直会等待到 thread() 运行结束。 正如在上面的例子中看到,一个特定的线程可以通过诸如 t 的变量访问,通过这个变量等待着它的使用 join() 方法终止。但是,即使 t 越界或者析构了,该线程也将继续执行。一个线程总是在一开始就绑定到一个类型为 boost::thread 的变量,但是一旦创建,就不在取决于它。甚至还存在着一个叫 detach() 的方法,允许类型为 boost::thread 的变量从它对应的线程里分离。当然了,像 join() 的方法之后也就不能被调用,因为这个变量不再是一个有效的线程。 任何一个函数内可以做的事情也可以在一个线程内完成。归根结底,一个线程只不过是一个函数,除了它是同时执行的。在上述例子中,使用一个循环把5个数字写入标准输出流。为了减缓输出,每一个循环中调用 wait() 函数让执行延迟了一秒。 wait() 可以调用一个名为 sleep() 的函数,这个函数也来自于 Boost.Thread,位于 boost::this_thread 名空间内。 sleep() 要么在预计的一段时间或一个特定的时间点后时才让线程继续执行。通过传递一个类型为 boost::posix_time::seconds 的对象,在这个例子里我们指定了一段时间。 boost::posix_time::seconds 来自于 Boost.DateTime 库,它被 Boost.Thread 用来管理和处理时间的数据。 虽然前面的例子说明了如何等待一个不同的线程,但下面的例子演示了如何通过所谓的中断点让一个线程中断。

代码语言:javascript
代码运行次数:0
复制
#include <boost/thread.hpp> // 引入 Boost 线程库#include <iostream> // 引入输入输出库​// 定义一个等待函数,接收一个整型参数 sec,表示等待的秒数void wait(int sec) {    // 调用 Boost 库中的 sleep 函数,使当前线程休眠指定的秒数    boost::this_thread::sleep(boost::posix_time::seconds(sec));}​// 定义线程执行的函数void thread() {    try {        // 循环 5 次        for(int i = 0; i < 5 ; ++ i) {            // 每次循环等待 1 秒            wait(1);            // 输出当前的循环计数            std::cout << i << std::endl;        }    }    // 捕获线程中断异常    catch(boost::thread_interrupted &) {        // 在此处可以处理线程被中断后的逻辑,当前代码为空    }}​int main(){    // 创建一个新的线程 t,并指定执行 thread 函数    boost::thread t(thread);    // 主线程等待 3 秒    wait(3);    // 中断线程 t 的执行    t.interrupt();    // 等待线程 t 执行完毕    t.join();}​// 输出 0 1

>>>在一个线程对象上调用 interrupt() 会中断相应的线程。在这方面,中断意味着一个类型为 boost::thread_interrupted 的异常,它会在这个线程中抛出。然后这只有在线程达到中断点时才会发生。 如果给定的线程不包含任何中断点,简单调用 interrupt() 就不会起作用。每当一个线程中断点,它就会检查 interrupt() 是否被调用过。只有被调用过了, boost::thread_interrupted 异常才会相应地抛出。 Boost.Thread定义了一系列的中断点,例如 sleep() 函数。由于 sleep() 在这个例子里被调用了五次,该线程就检查了五次它是否应该被中断。然而 sleep() 之间的调用,却不能使线程中断。 一旦该程序被执行,它只会打印三个数字到标准输出流。这是由于在main里3秒后调用 interrupt()方法。因此,相应的线程被中断,并抛出一个 boost::thread_interrupted 异常。这个异常在线程内也被正确地捕获, catch 处理虽然是空的。由于 thread() 函数在处理程序后返回,线程也被终止。这反过来也将终止整个程序,因为 main() 等待该线程使用join()终止该线程。 Boost.Thread定义包括上述 sleep()函数十个中断。有了这些中断点,线程可以很容易及时中断。然而,他们并不总是最佳的选择,因为中断点必须事前读入以检查 boost::thread_interrupted 异常。 为了提供一个对 Boost.Thread 里提供的多种函数的整体概述,下面的例子将会再介绍两个。

代码语言:javascript
代码运行次数:0
复制
#include <boost/thread.hpp> // 引入 Boost 线程库#include <iostream> // 引入输入输出库​int main(){    // 输出当前线程的 ID    std::cout << boost::this_thread::get_id() << std::endl;​    // 输出系统支持的最大并发线程数    std::cout << boost::thread::hardware_concurrency() << std::endl;​    return 0; // 返回 0,表示程序正常结束}​// 06:05:16: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...7f95a18027408

>>>使用 boost::this_thread命名空间,能提供独立的函数应用于当前线程,比如前面出现的 sleep() 。另一个是 get_id():它会返回一个当前线程的ID号。它也是由 boost::thread 提供的。 boost::thread 类提供了一个静态方法 hardware_concurrency() ,它能够返回基于CPU数目或者CPU内核数目的刻在同时在物理机器上运行的线程数。在常用的双核机器上调用这个方法,返回值为2。这样的话就可以确定在一个多核程序可以同时运行的理论最大线程数。

05、同步

>>>虽然多线程的使用可以提高应用程序的性能,但也增加了复杂性。如果使用线程在同一时间执行几个函数,访问共享资源时必须相应地同步。一旦应用达到了一定规模,这涉及相当一些工作。本段介绍了Boost.Thread提供同步线程的类。

代码语言:javascript
代码运行次数:0
复制
#include <boost/thread.hpp> // 引入 Boost 线程库#include <iostream> // 引入输入输出库​// 定义一个等待函数,接收一个整型参数 sec,表示等待的秒数void wait(int sec) {    // 调用 Boost 库中的 sleep 函数,使当前线程休眠指定的秒数    boost::this_thread::sleep(boost::posix_time::seconds(sec));}​// 创建一个互斥量,用于保护共享资源boost::mutex mutex;​// 定义线程执行的函数void thread() {    // 循环 5 次    for (int i = 0; i < 5; ++i)    {        wait(1); // 等待 1 秒​        // 获取互斥量锁,以保护输出不被其他线程干扰        mutex.lock();​        // 输出当前线程 ID 及循环计数        std::cout << "Thread " << boost::this_thread::get_id() << ": " << i << std::endl;​        // 解锁互斥量        mutex.unlock();    }}​int main(){    // 创建两个新的线程 t1 和 t2,并指定它们执行 thread 函数    boost::thread t1(thread);    boost::thread t2(thread);​    // 等待 t1 和 t2 两个线程执行完毕    t1.join();    t2.join();​    return 0; // 返回 0,表示程序正常结束}​// 输出Thread 7f2c1bf4c700: 0Thread 7f2c1b74b700: 0Thread 7f2c1bf4c700: 1Thread 7f2c1b74b700: 1Thread 7f2c1bf4c700: 2Thread 7f2c1b74b700: 2Thread 7f2c1bf4c700: 3Thread 7f2c1b74b700: 3Thread 7f2c1bf4c700: 4Thread 7f2c1b74b700: 4

>>>多线程程序使用所谓的互斥对象来同步。Boost.Thread提供多个的互斥类,boost::mutex是最简单的一个。互斥的基本原则是当一个特定的线程拥有资源的时候防止其他线程夺取其所有权。一旦释放,其他的线程可以取得所有权。这将导致线程等待至另一个线程完成处理一些操作,从而相应地释放互斥对象的所有权。 上面的示例使用一个类型为 boost::mutex 的 mutex 全局互斥对象。 thread() 函数获取此对象的所有权才在 for 循环内使用 lock() 方法写入到标准输出流的。一旦信息被写入,使用 unlock() 方法释放所有权。 main() 创建两个线程,同时执行 thread ()函数。利用 for 循环,每个线程数到5,用一个迭代器写一条消息到标准输出流。不幸的是,标准输出流是一个全局性的被所有线程共享的对象。该标准不提供任何保证 std::cout 可以安全地从多个线程访问。因此,访问标准输出流必须同步:在任何时候,只有一个线程可以访问 std::cout。 由于两个线程试图在写入标准输出流前获得互斥体,实际上只能保证一次只有一个线程访问 std::cout。不管哪个线程成功调用 lock() 方法,其他所有线程必须等待,直到 unlock() 被调用。 获取和释放互斥体是一个典型的模式,是由Boost.Thread通过不同的数据类型支持。例如,不直接地调用 lock() 和 unlock(),使用 boost::lock_guard 类也是可以的。

代码语言:javascript
代码运行次数:0
复制
#include <boost/thread.hpp> // 引入 Boost 线程库#include <iostream> // 引入输入输出库​// 定义一个等待函数,接收一个整型参数 sec,表示等待的秒数void wait(int sec) {    // 调用 Boost 库中的 sleep 函数,使当前线程休眠指定的秒数    boost::this_thread::sleep(boost::posix_time::seconds(sec));}​// 创建一个互斥量,用于保护共享资源boost::mutex mutex;​// 定义线程执行的函数void thread() {    // 循环 5 次    for (int i = 0; i < 5; ++i)    {        wait(1); // 等待 1 秒​        // 使用 lock_guard 自动管理互斥量的锁定和解锁        boost::lock_guard<boost::mutex> lock(mutex);​        // 输出当前线程 ID 及循环计数        std::cout << "Thread " << boost::this_thread::get_id() << ": " << i << std::endl;    }}​int main(){    // 创建两个新的线程 t1 和 t2,并指定它们执行 thread 函数    boost::thread t1(thread);    boost::thread t2(thread);​    // 等待 t1 和 t2 两个线程执行完毕    t1.join();    t2.join();​    return 0; // 返回 0,表示程序正常结束}​// 输出Thread 7f80f8d96700: 0Thread 7f80f8595700: 0Thread 7f80f8d96700: 1Thread 7f80f8595700: 1Thread 7f80f8d96700: 2Thread 7f80f8595700: 2Thread 7f80f8d96700: 3Thread 7f80f8595700: 3Thread 7f80f8595700: 4Thread 7f80f8d96700: 4

>>>boost::lock_guard 在其内部构造和析构函数分别自动调用 lock() 和 unlock() 。 访问共享资源是需要同步的,因为它显示地被两个方法调用。 boost::lock_guard 类是另一个出现在 第 2 章 智能指针 的RAII用语。 除了boost::mutex 和 boost::lock_guard 之外,Boost.Thread也提供其他的类支持各种同步。 其中一个重要的就是 boost::unique_lock ,相比较 boost::lock_guard 而言,它提供许多有用的方法。

代码语言:javascript
代码运行次数:0
复制
#include <boost/thread.hpp> // 引入 Boost 线程库#include <iostream> // 引入输入输出库​// 定义一个等待函数,接收一个整型参数 seconds,表示等待的秒数void wait(int seconds){    // 让当前线程休眠指定的秒数    boost::this_thread::sleep(boost::posix_time::seconds(seconds));}​// 创建一个带有时间限制的互斥量boost::timed_mutex mutex;​// 定义线程执行的函数void thread(){    // 循环 5 次    for (int i = 0; i < 5; ++i)    {        wait(1); // 等待 1 秒​        // 尝试获取互斥量锁        boost::unique_lock<boost::timed_mutex> lock(mutex, boost::try_to_lock);​        // 如果没有获得锁,尝试进行超时锁定        if (!lock.owns_lock())            lock.timed_lock(boost::get_system_time() + boost::posix_time::seconds(1));​        // 输出当前线程 ID 及循环计数        std::cout << "Thread " << boost::this_thread::get_id() << ": " << i << std::endl;​        // 释放锁并解锁        boost::timed_mutex *m = lock.release(); // 释放控制权,返回 mutex 指针        m->unlock(); // 手动解锁互斥量    }}​int main(){    // 创建两个新的线程 t1 和 t2,并指定它们执行 thread 函数    boost::thread t1(thread);    boost::thread t2(thread);​    // 等待 t1 和 t2 两个线程执行完毕    t1.join();    t2.join();​    return 0; // 返回 0,表示程序正常结束}​

>>>上面的例子用不同的方法来演示 boost::unique_lock 的功能。 当然了,这些功能的用法对给定的情景不一定适用;boost::lock_guard 在上个例子的用法还是挺合理的。 这个例子就是为了演示 boost::unique_lock 提供的功能。 boost::unique_lock 通过多个构造函数来提供不同的方式获得互斥体。 这个期望获得互斥体的函数简单地调用了 lock() 方法,一直等到获得这个互斥体。 所以它的行为跟 boost::lock_guard 的那个是一样的。 如果第二个参数传入一个 boost::try_to_lock 类型的值,对应的构造函数就会调用 try_lock() 方法。 这个方法返回 bool 型的值:如果能够获得互斥体则返回true,否则返回 false 。 相比 lock() 函数,try_lock() 会立即返回,而且在获得互斥体之前不会被阻塞。 上面的程序向 boost::unique_lock 的构造函数的第二个参数传入boost::try_to_lock。 然后通过 owns_lock() 可以检查是否可获得互斥体。 如果不能, owns_lock() 返回 false。 这也用到 boost::unique_lock 提供的另外一个函数: timed_lock() 等待一定的时间以获得互斥体。 给定的程序等待长达1秒,应较足够的时间来获取更多的互斥。 其实这个例子显示了三个方法获取一个互斥体:lock() 会一直等待,直到获得一个互斥体。 try_lock() 则不会等待,但如果它只会在互斥体可用的时候才能获得,否则返回 false 。 最后,timed_lock() 试图获得在一定的时间内获取互斥体。 和 try_lock() 一样,返回bool 类型的值意味着成功是否。 虽然 boost::mutex 提供了 lock() 和 try_lock() 两个方法,但是 boost::timed_mutex 只支持 timed_lock() ,这就是上面示例那么使用的原因。 如果不用 timed_lock() 的话,也可以像以前的例子那样用 boost::mutex。 就像 boost::lock_guard 一样, boost::unique_lock 的析构函数也会相应地释放互斥量。此外,可以手动地用 unlock() 释放互斥量。也可以像上面的例子那样,通过调用 release() 解除boost::unique_lock 和互斥量之间的关联。然而在这种情况下,必须显式地调用 unlock() 方法来释放互斥量,因为 boost::unique_lock 的析构函数不再做这件事情。 boost::unique_lock 这个所谓的独占锁意味着一个互斥量同时只能被一个线程获取。 其他线程必须等待,直到互斥体再次被释放。 除了独占锁,还有非独占锁。 Boost.Thread里有个 boost::shared_lock 的类提供了非独占锁。 正如下面的例子,这个类必须和 boost::shared_mutex 型的互斥量一起使用。

代码语言:javascript
代码运行次数:0
复制
#include <boost/thread.hpp> // 引入 Boost 线程库#include <iostream> // 引入输入输出库#include <vector> // 引入向量库#include <cstdlib> // 引入 C 标准库(用于生成随机数)#include <ctime> // 引入时间库(用于种子设置)​// 定义一个等待函数,接收一个整型参数 seconds,表示等待的秒数void wait(int seconds){    // 让当前线程休眠指定的秒数    boost::this_thread::sleep(boost::posix_time::seconds(seconds));}​// 创建一个共享互斥量,用于保护共享资源boost::shared_mutex mutex;std::vector<int> random_numbers; // 存放随机数的向量​// 填充随机数的线程执行函数void fill(){    std::srand(static_cast<unsigned int>(std::time(0))); // 使用当前时间作为种子    for (int i = 0; i < 3; ++i)    {        // 获取独占锁,以允许写操作        boost::unique_lock<boost::shared_mutex> lock(mutex);        random_numbers.push_back(std::rand()); // 生成并添加随机数        std::cout << "fill = " << random_numbers.back() << std::endl;        // 手动解锁(在此处可以省略,因为 lock 超出作用域后会自动解锁)        lock.unlock();        wait(1); // 等待 1 秒    }}​// 打印随机数的线程执行函数void print(){    for (int i = 0; i < 3; ++i)    {        wait(1); // 等待 1 秒        // 获取共享锁,以允许读操作        boost::shared_lock<boost::shared_mutex> lock(mutex);        std::cout << "print = " << random_numbers.back() << std::endl; // 打印随机数    }}​// 全局变量 sum,用于计算随机数和int sum = 0;​// 计算随机数总和的线程执行函数void count(){    for (int i = 0; i < 3; ++i)    {        wait(1); // 等待 1 秒        // 获取共享锁,以允许读操作        boost::shared_lock<boost::shared_mutex> lock(mutex);        sum += random_numbers.back(); // 计算总和    }}​int main(){    // 创建三个线程:一个用于填充随机数、一个用于打印随机数、一个用于计算总和    boost::thread t1(fill);    boost::thread t2(print);    boost::thread t3(count);​    // 等待 t1、t2 和 t3 三个线程执行完毕    t1.join();    t2.join();    t3.join();​    // 输出随机数的总和    std::cout << "Sum: " << sum << std::endl;​    return 0; // 返回 0,表示程序正常结束}// 输出06:52:41: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...fill = 1490232041print = 1490232041fill = 127494666print = 127494666fill = 344761622print = 344761622Sum: 1962488329

>>>boost::shared_lock 类型的非独占锁可以在线程只对某个资源读访问的情况下使用。 一个线程修改的资源需要写访问,因此需要一个独占锁。 这样做也很明显:只需要读访问的线程不需要知道同一时间其他线程是否访问。 因此非独占锁可以共享一个互斥体。 在给定的例子, print() 和 count() 都可以只读访问 random_numbers 。 虽然 print() 函数把 random_numbers 里的最后一个数写到标准输出,count() 函数把它统计到 sum 变量。 由于没有函数修改 random_numbers,所有的都可以在同一时间用 boost::shared_lock 类型的非独占锁访问它。 在 fill() 函数里,需要用一个 boost::unique_lock 类型的非独占锁,因为它插入了一个新的随机数到 random_numbers。 在 unlock() 显式地调用 unlock() 来释放互斥量之后, fill() 等待了一秒。 相比于之前的那个样子, 在 for 循环的尾部调用 wait() 以保证容器里至少存在一个随机数,可以被print() 或者 count() 访问。 对应地,这两个函数在 for 循环的开始调用了 wait() 。 考虑到在不同的地方每个单独地调用 wait() ,一个潜在的问题变得很明显:函数调用的顺序直接受CPU执行每个独立进程的顺序决定。 利用所谓的条件变量,可以同步哪些独立的线程,使数组的每个元素都被不同的线程立即添加到 random_numbers 。

代码语言:javascript
代码运行次数:0
复制
#include <boost/thread.hpp>  // 引入Boost线程库#include <iostream>          // 引入输入输出流#include <vector>           // 引入向量库#include <cstdlib>          // 引入标准库中有用的函数#include <ctime>            // 引入时间库​// 声明全局变量boost::mutex mutex;                      // 互斥锁,确保线程安全boost::condition_variable_any cond;      // 条件变量,用于线程间的同步std::vector<int> random_numbers;         // 存储生成的随机数的向量​// 填充随机数的函数void fill(){    std::cout << "fill()" << std::endl;    //std::srand(static_cast<unsigned int>(std::time(0))); // 用当前时间作为随机数种子    for (int i = 0; i < 3; ++i)  // 循环三次以生成三个随机数    {        boost::unique_lock<boost::mutex> lock(mutex); // 锁定互斥量        random_numbers.push_back(i);        // 生成随机数并添加到向量中        cond.notify_all();                  // 通知所有等待的线程        cond.wait(mutex);                   // 等待条件变量    }}​// 打印随机数的函数void print(){    std::cout << "print()" << std::endl;    std::size_t next_size = 1; // 期望的随机数个数,从1开始    for (int i = 0; i < 3; ++i) // 循环三次以打印三个随机数    {        boost::unique_lock<boost::mutex> lock(mutex); // 锁定互斥量        std::cout << "random_numbers.size() = " << random_numbers.size() << std::endl;        std::cout << "next_size = " << next_size << std::endl;​        while (random_numbers.size() != next_size) {            std::cout << "while" << std::endl;            cond.wait(mutex);                         // 如果没有达到,则等待条件变量        }        std::cout << random_numbers.back() << std::endl; // 打印最后一个生成的随机数        ++next_size;                                   // 期望的随机数个数加一        cond.notify_all();                             // 通知所有等待的线程    }}​// 主函数int main(){    boost::thread t1(fill); // 创建一个线程来执行fill函数    boost::thread t2(print); // 创建另一个线程来执行print函数    t1.join();               // 等待t1线程结束    t2.join();               // 等待t2线程结束}// 04:53:24: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...fill()print()random_numbers.size() = 1next_size = 10random_numbers.size() = 1next_size = 2while1random_numbers.size() = 2next_size = 3while2

>>>这个例子的程序删除了 wait() 和 count() 。线程不用在每个循环迭代中等待一秒,而是尽可能快地执行。此外,没有计算总额;数字完全写入标准输出流。 为确保正确地处理随机数,需要一个允许检查多个线程之间特定条件的条件变量来同步不每个独立的线程。 正如上面所说, fill() 函数用在每个迭代产生一个随机数,然后放在 random_numbers 容器中。 为了防止其他线程同时访问这个容器,就要相应得使用一个排它锁。 不是等待一秒,实际上这个例子却用了一个条件变量。 调用 notify_all() 会唤醒每个哪些正在分别通过调用wait() 等待此通知的线程。 通过查看 print() 函数里的 for 循环,可以看到相同的条件变量被 wait() 函数调用了。 如果这个线程被 notify_all() 唤醒,它就会试图这个互斥量,但只有在 fill() 函数完全释放之后才能成功。 这里的窍门就是调用 wait() 会释放相应的被参数传入的互斥量。 在调用 notify_all()后, fill() 函数会通过 wait() 相应地释放线程。 然后它会阻止和等待其他的线程调用 notify_all() ,一旦随机数已写入标准输出流,这就会在 print() 里发生。 注意到在 print() 函数里调用 wait() 事实上发生在一个单独 while 循环里。 这样做的目的是为了处理在 print() 函数里第一次调用 wait() 函数之前随机数已经放到容器里。 通过比较 random_numbers 里元素的数目与预期值,发现这成功地处理了把随机数写入到标准输出流。

11、线程本地存储

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

代码语言:javascript
代码运行次数:0
复制
#include <boost/thread.hpp>  // 引入Boost线程库#include <iostream>          // 引入输入输出流#include <cstdlib>          // 引入标准库中有用的函数#include <ctime>            // 引入时间库​// 初始化随机数生成器的函数void init_number_generator(){    static bool done = false; // 使用静态变量来确保初始化仅执行一次    if (!done)                // 检查是否已完成初始化    {        done = true;         // 将done标记为true,表示初始化已完成        std::srand(static_cast<unsigned int>(std::time(0))); // 用当前时间作为随机数种子    }}​// 创建互斥锁,用于保护共享资源boost::mutex mutex;​// 随机数生成函数void random_number_generator(){    init_number_generator(); // 调用初始化函数,确保随机数生成器只初始化一次    int i = std::rand()%10;     // 生成一个随机数    boost::lock_guard<boost::mutex> lock(mutex); // 锁定互斥锁,确保线程安全    std::cout << i  << std::endl; // 输出生成的随机数 避免多个线程同时访问导致输出混乱。}​// 主函数int main(){    boost::thread t[3]; // 创建一个包含三个线程的数组​    // 创建三个线程,每个线程执行random_number_generator函数    for (int i = 0; i < 3; ++i)        t[i] = boost::thread(random_number_generator);​    // 等待所有线程执行完毕    for (int i = 0; i < 3; ++i)        t[i].join();}// 05:09:43: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...163

>>>该示例创建三个线程,每个线程写一个随机数到标准输出流。 random_number_generator() 函数将会利用在C++标准里定义的 std::rand() 函数创建一个随机数。 但是用于 std::rand() 的随机数产生器必须先用 std::srand() 正确地初始化。 如果没做,程序始终打印同一个随机数。 随机数产生器,通过 std::time() 返回当前时间, 在 init_number_generator() 函数里完成初始化。 由于这个值每次都不同,可以保证产生器总是用不同的值初始化,从而产生不同的随机数。 因为产生器只要初始化一次, init_number_generator() 用了一个静态变量 done 作为条件量。 如果程序运行了多次,写入的三分之二的随机数显然就会相同。 事实上这个程序有个缺陷:std::rand() 所用的产生器必须被各个线程初始化。 因此 init_number_generator() 的实现实际上是不对的,因为它只调用了一次 std::srand() 。使用TLS,这一缺陷可以得到纠正。

代码语言:javascript
代码运行次数:0
复制
#include <boost/thread.hpp>  // 引入Boost线程库#include <iostream>          // 引入输入输出流#include <cstdlib>          // 引入标准库中有用的函数#include <ctime>            // 引入时间库​// 初始化随机数生成器的函数void init_number_generator(){    static boost::thread_specific_ptr<bool> tls; // 声明一个线程局部存储的指针    if (!tls.get()) // 检查线程局部存储是否已初始化        tls.reset(new bool(false)); // 如果未初始化,则为其分配一个新布尔值,初始为false    if (!*tls) // 检查是否已经初始化过随机数生成器    {        *tls = true; // 标记为已初始化        std::srand(static_cast<unsigned int>(std::time(0))); // 使用当前时间作为随机数种子    }}​// 创建互斥锁,用于保护共享资源boost::mutex mutex;​// 随机数生成函数void random_number_generator(){    init_number_generator(); // 调用初始化函数,确保每个线程的随机数生成器只初始化一次    int i = std::rand(); // 生成一个随机数    boost::lock_guard<boost::mutex> lock(mutex); // 锁定互斥锁,确保线程安全    std::cout << i << std::endl; // 输出生成的随机数}​// 主函数int main(){    boost::thread t[3]; // 创建一个包含三个线程的数组​    // 创建三个线程,每个线程执行random_number_generator函数    for (int i = 0; i < 3; ++i)        t[i] = boost::thread(random_number_generator);​    // 等待所有线程执行完毕    for (int i = 0; i < 3; ++i)        t[i].join();}​

>>>用一个TLS变量 tls 代替静态变量 done,是基于用 bool 类型实例化的 boost::thread_specific_ptr 。 原则上, tls 工作起来就像 done :它可以作为一个条件指明随机数发生器是否被初始化。 但是关键的区别,就是 tls 存储的值只对相应的线程可见和可用。 一旦一个 boost::thread_specific_ptr 型的变量被创建,它可以相应地设置。 不过,它期望得到一个 bool 型变量的地址,而非它本身。使用 reset() 方法,可以把它的地址保存到 tls 里面。 在给出的例子中,会动态地分配一个 bool 型的变量,由 new 返回它的地址,并保存到 tls 里。 为了避免每次调用 init_number_generator() 都设置 tls ,它会通过 get() 函数检查是否已经保存了一个地址。 由于 boost::thread_specific_ptr 保存了一个地址,它的行为就像一个普通的指针。 因此,operator*() 和 operator->() 都被被重载以方便使用。 这个例子用 *tls 检查这个条件当前是 true 还是 false。 再根据当前的条件,随机数生成器决定是否初始化。 正如所见, boost::thread_specific_ptr 允许为当前进程保存一个对象的地址,然后只允许当前进程获得这个地址。 然而,当一个线程已经成功保存这个地址,其他的线程就会可能就失败。 如果程序正在执行时,它可能会令人感到奇怪:尽管有了TLS的变量,生成的随机数仍然相等。 这是因为,三个线程在同一时间被创建,从而造成随机数生成器在同一时间初始化。 如果该程序执行了几次,随机数就会改变,这就表明生成器初始化正确了。

总结

>>>程序员请对自己好点,没人关心你,我关心。秃头真的影响帅气

Qt | ubuntu20.04+boost_1_86_0搭建和编译(使用Qt6.5.3中运行测试程序)

Boost C++ 库 | 是什么?

Boost C++ 库 | 智能指针(RAII、作用域指针、作用域数组)

Boost C++ 库 | 智能指针(共享指针、共享数组、弱指针、介入式指针、指针容器)入门

Boost C++ 库 | 函数对象(数百家企业面试题C++分享)

Boost C++ 库 | 事件处理

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 点击上方"蓝字"关注我们
  • 01、序言
  • 05、同步
  • 11、线程本地存储
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档