C++ Boost Thread线程使用示例详解

C/C++
370
0
0
2023-06-19
目录
  • 一、并行编程
  • 二、生成何管理Threads
  • 练习

一、并行编程

以下库支持并行编程模型。

  • Boost.Thread 允许您创建和管理自己的线程。
  • Boost.Atomic 允许您通过多个线程的原子操作访问整数类型的变量。
  • Boost.Lockfree 提供线程安全的容器。
  • Boost.MPI 起源于超级计算机领域。使用 Boost.MPI,您的程序可以多次启动并在多个进程中执行。您专注于对应该并发执行的实际任务进行编程,而 Boost.MPI 会协调这些过程。使用 Boost.MPI,您无需处理诸如同步访问共享数据之类的细节。但是,Boost.MPI 确实需要适当的运行时环境。

二、生成何管理Threads

        这个库中最重要的类是 boost::thread,它在 boost/thread.hpp 中定义。此类用于创建新线程。示例 44.1 是一个创建线程的简单示例。

        例 44.1。使用 boost::thread

#include <boost/thread.hpp>
#include <boost/chrono.hpp>
#include <iostream>
void wait(int seconds)
{
  boost::this_thread::sleep_for(boost::chrono::seconds{seconds});
}
void thread()
{
  for (int i =; i < 5; ++i)
  {
    wait();
    std::cout << i << '\n';
  }
}
int main()
{
  boost::thread t{thread};
  t.join();
}

        新线程应该执行的函数的名称被传递给 boost::thread 的构造函数。一旦示例 44.1 中的变量 t 被创建,函数 thread() 立即开始在它自己的线程中执行。此时,thread() 与 main() 函数同时执行。

        为了防止程序终止,在新创建的线程上调用 join()。 join() 阻塞当前线程,直到为其调用 join() 的线程终止。这会导致 main() 等待直到 thread() 返回。

        可以使用变量访问特定线程 - 在本示例中为 t t - 以等待其终止。但是,即使 t 超出范围并被销毁,线程仍将继续执行。线程一开始总是绑定到 boost::thread 类型的变量,但一旦创建,线程就不再依赖于该变量。甚至还有一个名为 detach() 的成员函数,它允许类型为 boost::thread 的变量与其对应的线程分离。不可能在调用 detach() 之后调用像 join() 这样的成员函数,因为分离的变量不再代表有效的线程。

        任何可以在函数内完成的事情也可以在线程内完成。归根结底,线程与函数没有什么不同,只是它与另一个函数并发执行。在例 44.1 中,循环中将五个数字写入标准输出流。为了减慢输出速度,循环的每次迭代都会调用 wait() 函数来暂停一秒钟。 wait() 使用函数 sleep_for() ,它也由 Boost.Thread 提供并位于命名空间 boost::this_thread 中。

        sleep_for() 需要一个时间段作为其唯一参数,该时间段指示当前线程应该停止多长时间。通过传递类型为 boost::chrono::seconds 的对象,可以设置一段时间。 boost::chrono::seconds 来自第 37 章介绍的 Boost.Chrono。

        sleep_for() 只接受来自 Boost.Chrono 的类型。尽管 Boost.Chrono 已成为 C++11 标准库的一部分,但来自 std::chrono 的类型不能与 Boost.Thread 一起使用。这样做会导致编译器错误。

        如果您不想在 main() 结束时调用 join(),您可以使用类 boost::scoped_thread。

        示例 44.2。使用 boost::scoped_thread 等待线程

#include <boost/thread.hpp>
#include <boost/thread/scoped_thread.hpp>
#include <boost/chrono.hpp>
#include <iostream>
void wait(int seconds)
{
  boost::this_thread::sleep_for(boost::chrono::seconds{seconds});
}
void thread()
{
  for (int i =; i < 5; ++i)
  {
    wait();
    std::cout << i << '\n';
  }
}
int main()
{
  boost::scoped_thread<> t{boost::thread{thread}};
}

        boost::scoped_thread 的构造函数需要一个 boost::thread 类型的对象。在 boost::scoped_thread 的析构函数中,一个动作可以访问该对象。默认情况下,boost::scoped_thread 使用在线程上调用 join() 的操作。因此,示例 44.2 的工作方式类似于示例 44.1。

        您可以将用户定义的操作作为模板参数传递。该操作必须是一个带有运算符 operator() 的类,该运算符接受 boost::thread 类型的对象。 boost::scoped_thread 保证运算符将在析构函数中调用。

        您只能在 Boost.Thread 中找到类 boost::scoped_thread。标准库中没有对应的。确保包含 boost::scoped_thread 的头文件 boost/thread/scoped_thread.hpp。

        示例 44.3 引入了中断点,这使得中断线程成为可能。中断点仅由 Boost.Thread 支持,标准库不支持。

        示例 44.3。 boost::this_thread::sleep_for() 的中断点

#include <boost/thread.hpp>
#include <boost/chrono.hpp>
#include <iostream>
void wait(int seconds)
{
  boost::this_thread::sleep_for(boost::chrono::seconds{seconds});
}
void thread()
{
  try
  {
    for (int i =; i < 5; ++i)
    {
      wait();
      std::cout << i << '\n';
    }
  }
  catch (boost::thread_interrupted&) {}
}
int main()
{
  boost::thread t{thread};
  wait();
  t.interrupt();
  t.join();
}

        在线程对象上调用 interrupt() 会中断相应的线程。在此上下文中,中断意味着在线程中抛出类型为 boost::thread_interrupted 的异常。但是,这仅在线程到达中断点时发生。

        如果给定的线程不包含中断点,则简单地调用 interrupt() 不会有任何效果。每当线程到达中断点时,它都会检查是否已调用 interrupt()。如果它已被调用,将抛出 boost::thread_interrupted 类型的异常。

        Boost.Thread 定义了一系列中断点,例如 sleep_for() 函数。因为在示例 44.3 中 sleep_for() 被调用了五次,线程检查了五次它是否被中断。在对 sleep_for() 的调用之间,线程不能被中断。

        示例 44.3 没有显示五个数字,因为在 main() 中三秒后调用了 interrupt()。因此,相应的线程被中断并抛出 boost::thread_interrupted 异常。即使捕获处理程序为空,异常也会在线程内被正确捕获。因为 thread() 函数在处理程序之后返回,所以线程也会终止。反过来,这将导致程序终止,因为 main() 正在等待线程终止。

        Boost.Thread 定义了大约十五个中断点,包括 sleep_for()。这些中断点使得及时中断线程变得容易。然而,中断点可能并不总是最好的选择,因为它们必须在线程可以检查 boost::thread_interrupted 异常之前到达。

        示例 44.4。使用 disable_interruption 禁用中断点

#include <boost/thread.hpp>
#include <boost/chrono.hpp>
#include <iostream>
void wait(int seconds)
{
  boost::this_thread::sleep_for(boost::chrono::seconds{seconds});
}
void thread()
{
  boost::this_thread::disable_interruption no_interruption;
  try
  {
    for (int i =; i < 5; ++i)
    {
      wait();
      std::cout << i << '\n';
    }
  }
  catch (boost::thread_interrupted&) {}
}
int main()
{
  boost::thread t{thread};
  wait();
  t.interrupt();
  t.join();
}

        类 boost::this_thread::disable_interruption 防止线程被中断。如果实例化 boost::this_thread::disable_interruption,只要对象存在,线程中的中断点就会被禁用。因此,示例 44.4 显示了五个数字,因为中断线程的尝试被忽略了。

        示例 44.5。使用 boost::thread::attributes 设置线程属性

#include <boost/thread.hpp>
#include <boost/chrono.hpp>
#include <iostream>
void wait(int seconds)
{
  boost::this_thread::sleep_for(boost::chrono::seconds{seconds});
}
void thread()
{
  try
  {
    for (int i =; i < 5; ++i)
    {
      wait();
      std::cout << i << '\n';
    }
  }
  catch (boost::thread_interrupted&) {}
}
int main()
{
  boost::thread::attributes attrs;
  attrs.set_stack_size();
  boost::thread t{attrs, thread};
  t.join();
}

boost::thread::attributes 用于设置线程属性。在 1.56.0 版本中,您只能设置一个与平台无关的属性,即堆栈大小。在示例 44.5 中,堆栈大小由 boost::thread::attributes::set_stack_size() 设置为 1024 字节。

示例 44.6。检测线程 ID 和可用处理器的数量

#include <boost/thread.hpp>
#include <iostream>
int main()
{
  std::cout << boost::this_thread::get_id() << '\n';
  std::cout << boost::thread::hardware_concurrency() << '\n';
}

        在命名空间 boost::this_thread 中,定义了适用于当前线程的独立函数。其中一个函数是我们之前见过的 sleep_for()。另一个是 get_id(),它返回一个数字以唯一标识当前线程(参见示例 44.6)。 get_id() 也作为类 boost::thread 的成员函数提供。

        静态成员函数 boost::thread::hardware_concurrency() 返回物理上可以同时执行的线程数,基于 CPU 或 CPU 内核的基础数量。在双核处理器上调用此函数返回值 2。此函数提供了一种简单的方法来确定理论上应该使用的最大线程数。

        Boost.Thread 还提供类 boost::thread_group 来管理组中的线程。此类提供的一个函数是成员函数 join_all(),它等待组中的所有线程终止。

练习

使用两个线程计算在 for 循环中相加的所有数字的总和:

#include <boost/timer/timer.hpp>
#include <iostream>
#include <cstdint>
int main()
{
    boost::timer::cpu_timer timer;
    std::uint_t total = 0;
    for (int i =; i < 1'000'000'000; ++i)
        total += i;
    std::cout << timer.format();
    std::cout << total << '\n';
}

概括该程序,使其使用尽可能多的线程,可以在计算机上并发执行。例如,如果程序在具有四核 CPU 的计算机上运行,​​则该程序应该使用四个线程。