C++11中的线程讲解

2023-11-26 15:13:20 浏览数 (1)

定义

线程是一种轻量级的执行单元,它可以独立运行并与其他线程并发执行。通过创建多个线程,可以实现并行计算、异步任务处理和提高程序的响应性。

多线程

std::thread 类, 位于 <thread> 头文件,实现了线程操作。std::thread 可以和普通函数和 lambda 表达式搭配使用。它还允许向线程的执行函数传递任意多参数。

代码语言:txt复制
 
 void func()
{
   // do some work
}
 
int main()
{
   std::thread t(func);
   t.join();
   return 0;
}

上面的例子中,t 是一个线程实例,函数 func() 在该线程运行。调用 join() 函数是为了阻塞当前线程(此处即主线程),直到 t 线程执行完毕。线程函数的返回值都会被忽略,但线程函数接受任意数目的输入参数。

代码语言:txt复制
void func(int i, double d, const std::string& s)
{
    std::cout << i << ", " << d << ", " << s << std::endl;
}

int main()
{
   std::thread t(func, 1, 12.50, "sample");
   t.join();
       
   return 0;

}
}

虽然可以向线程函数传递任意多参数,但都必须以值传递。如果需以引用传递,则必须以 std::ref 或 std::cref 封装,如下例所示:

代码语言:txt复制
void func(int& a)
{
   a  ;
}
 
 int main()
{
   int a = 42;
   std::thread t(func, std::ref(a));
   t.join();

   std::cout << a << std::endl;

   return 0;
}

这个程序会打印43,但如果不用 std::ref 封装,则输出会是42。

除了 join 函数,这个类还提供更多的操作:

  • swap:交换两个线程实例的句柄
  • detach:允许一个线程继续独立于线程实例运行;detach 过的线程不可以再 join
代码语言:txt复制
int main()
{
    std::thread t(funct);
    t.detach();

    return 0;
}

一个重要的知识点是,如果一个线程函数抛出异常,并不会被常规的 try-catch 方法捕获。也就是说,下面的写法是不会奏效的:

代码语言:txt复制
try
{
    std::thread t1(func);
    std::thread t2(func);

    t1.join();
    t2.join();
}
catch(const std::exception& ex)
{
    std::cout << ex.what() << std::endl;
}

要追踪线程间的异常,你可以在线程函数内捕获,暂时存储在一个稍后可以访问的结构内。

代码语言:txt复制
std::mutex                       g_mutex;
std::vector<std::exception_ptr>  g_exceptions;

void throw_function()
{
   throw std::exception("something wrong happened");
}

void func()
{
   try
   {
      throw_function();
   }
   catch(...)
   {
      std::lock_guard<std::mutex> lock(g_mutex);
      g_exceptions.push_back(std::current_exception());
   }
}

int main()
{
   g_exceptions.clear();

   std::thread t(func);
   t.join();

   for(auto& e : g_exceptions)
   {
      try 
      {
         if(e != nullptr)
         {
            std::rethrow_exception(e);
         }
      }
      catch(const std::exception& e)
      {
         std::cout << e.what() << std::endl;
      }
   }

   return 0;
}

关于捕获和处理异常,更深入的信息可以参看 Handling C exceptions thrown from worker thread in the main thread 和 How can I propagate exceptions between threads? 。

此外,值得注意的是, 头文件还在 std::this_thread 命名空间下提供了一些辅助函数:

  • get_id: 返回当前线程的 id
  • yield: 告知调度器运行其他线程,可用于当前处于繁忙的等待状态
  • sleep_for:给定时长,阻塞当前线程
  • sleep_until:阻塞当前线程至给定时间点

创建线程:

使用std::thread类创建新的线程,需要传入一个可调用对象(函数指针、函数对象、Lambda 表达式等)作为线程的执行体。

  • 线程的创建是相对简单的,可以利用多线程来实现并行计算、异步任务处理等。
  • 在创建线程时,要考虑线程的启动开销和资源的分配情况。

线程管理:

通过std::thread类提供的成员函数进行线程的管理,如等待线程结束、分离线程等。

  • 使用std::thread::join()函数在主线程中等待子线程执行结束。
  • 使用std::thread::detach()函数将线程与主线程分离,让其在后台执行。

线程间共享数据和同步:

在线程之间共享数据时,需要注意线程安全和同步机制。

  • 可以使用互斥锁、条件变量等同步机制来保护共享数据的访问,避免竞态条件和数据竞争。
  • 合理使用同步机制可以确保线程间的数据一致性和协调性。

异常处理:

在多线程环境下,线程中抛出的异常无法被主线程捕获,需要使用std::promisestd::future等机制来传递异常信息。

  • 合理处理线程中的异常,保证程序的稳定性和可靠性。

性能考虑:

多线程编程可以提高程序的性能和效率,但也需要考虑线程的开销、资源竞争和线程安全等问题。

  • 合理控制线程的数量,避免过多的线程引起的资源竞争和上下文切换开销。
  • 选择合适的同步机制,避免过度的锁竞争和阻塞。

设计并发算法:

在设计并发算法时,需要考虑线程之间的通信、同步和负载均衡等问题。

  • 使用合适的数据结构和算法,减少线程之间的竞争和锁冲突。
  • 利用任务分解和并行计算等技术,提高并发性能和效率。

C 11中的线程库为我们提供了方便且强大的多线程编程能力,可以实现并发和并行的程序设计。在使用线程时,我们需要要考虑线程安全、同步机制和性能优化等方面的问题,确保程序的正确性、可靠性和高效性。

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

0 人点赞