您的位置:

C++11多线程:并发编程、异步任务、原子操作、锁机制和线程池

C++11引入了许多新的特性,其中包括对多线程编程的支持。本文将介绍C++11中多线程编程的各种方面,包括并发编程、异步任务、原子操作、锁机制以及线程池等。

一、并发编程

并发编程是指在一个应用程序中同时执行多个任务的能力。多线程是实现并发编程的一种常见方式。C++11为多线程编程提供了一组重要的工具,包括线程、互斥锁、条件变量、原子操作和同步队列。下面分别介绍这些内容。

1. 线程

线程是指进程中执行的并发任务。C++11中的线程类是std::thread,使用它可以创建和控制线程。下面是一个简单的线程示例,展示了如何创建和启动一个新的线程。
#include 
#include 
   

void thread_function()
{
   std::cout << "thread function running" << std::endl;
}

int main()
{
   std::thread t(&thread_function);
   std::cout << "main thread running" << std::endl;
   t.join();
   return 0;
}

   
  
在上面的示例中,std::thread对象t是一个新线程,它的构造函数接受线程函数和函数参数。在这个示例中,线程函数是thread_function(),没有参数。线程对象t被创建后,可以调用它的join()方法来等待线程完成。

2. 互斥锁和条件变量

在多线程程序中,对共享数据的并发访问可能会导致问题。为了避免这些问题,C++11提供了互斥锁和条件变量。互斥锁可以用于同步多个线程之间的访问,条件变量可以用于在线程之间传递信号。下面是使用互斥锁和条件变量的示例:
#include 
#include 
   
#include 
    
#include 
     
#include 
      

std::mutex mtx;
std::condition_variable cv;
std::queue
       
        q; bool ready = false; void producer_thread() { for (int i = 0; i < 5; ++i) { std::unique_lock
        
         lock(mtx); q.push(i); std::cout << "producer thread pushed " << i << std::endl; ready = true; cv.notify_one(); } } void consumer_thread() { while (true) { std::unique_lock
         
          lock(mtx); while (!ready) cv.wait(lock); if (q.empty()) break; int data = q.front(); q.pop(); std::cout << "consumer thread got " << data << std::endl; ready = false; } } int main() { std::thread producer(producer_thread); std::thread consumer(consumer_thread); producer.join(); consumer.join(); return 0; }
         
        
       
      
     
    
   
  
在上面的示例中,我们使用了互斥锁和条件变量来同步生产者和消费者线程。生产者线程调用cv.notify_one()来通知消费者线程可以取数据了,消费者线程使用cv.wait(lock)来等待生产者线程的通知。

二、异步任务

异步任务是指程序中可以单独执行、不阻塞主线程的任务。C++11为异步任务提供了一个很方便的方式:std::async()。使用std::async()可以在新的线程中异步执行一个函数,同时返回一个std::future对象,该对象可以用于获取函数的返回值或者等待函数执行完成。下面是std::async()的一个示例:
#include 
#include 
   

int async_function()
{
   std::cout << "async_function running" << std::endl;
   return 42;
}

int main()
{
   std::future
     f = std::async(std::launch::async, &async_function);
   std::cout << "main thread running" << std::endl;
   std::cout << "async_function returned " << f.get() << std::endl;
   return 0;
}

    
   
  
在上面的示例中,std::async()函数被用来异步执行函数async_function()。该函数返回一个std::future对象f,用于获取异步函数的返回值。在主函数中,我们使用f.get()方法来等待异步函数执行完成并且获取其返回值。

三、原子操作和锁机制

原子操作是指可以在没有锁的情况下对共享数据进行读写操作的语句。C++11中提供了std::atomic类来支持原子操作。std::atomic类可以确保任何对原子对象的操作都是对整个值进行原子化的,因此不会发生竞争条件。下面是一个原子操作的示例:
#include 
#include 
   
#include 
    

std::atomic
      counter(0);

void thread_function()
{
   for (int i = 0; i < 10000; ++i) {
      counter++;
   }
}

int main()
{
   std::thread t1(thread_function);
   std::thread t2(thread_function);
   t1.join();
   t2.join();
   std::cout << "counter = " << counter << std::endl;
   return 0;
}

     
    
   
  
上面的示例中,我们使用std::atomic 来定义了一个原子化的计数器。在两个线程中分别对其进行了10000次自增操作。由于使用了std::atomic ,所以两个线程对计数器的自增操作是原子化的,不会导致数据竞争。最终输出的counter的值为20000。 锁机制也是避免多线程访问共享数据时出现数据竞争的一种重要手段。C++11提供了多种类型的锁来支持多线程程序。下面是使用std::mutex进行线程同步的示例:
#include 
#include 
     
#include 
      

std::mutex mtx;
int counter = 0;

void thread_function()
{
   for (int i = 0; i < 10000; ++i) {
      std::unique_lock
        lock(mtx);
      counter++;
   }
}

int main()
{
   std::thread t1(thread_function);
   std::thread t2(thread_function);
   t1.join();
   t2.join();
   std::cout << "counter = " << counter << std::endl;
   return 0;
}

       
      
     
    
在上面的示例中,我们使用std::mutex来定义了一个互斥锁。在两个线程中对计数器进行自增前,先进行了加锁操作,以保证操作的原子性和线程安全。最终计数器的值也是20000。

四、线程池

线程池是一种常见的并发编程技术,通过维护一组线程,以待执行的任务队列作为输入,执行任务队列中的任务,以实现多线程并发执行。C++11中并没有提供线程池的实现,但我们可以自己实现一个简单的线程池。下面是一个线程池的示例:
#include 
#include 
     
#include 
      
#include 
       
#include 
        

class ThreadPool
{
public:
   ThreadPool(std::size_t n) : stopped(false)
   {
      for (std::size_t i = 0; i < n; ++i) {
         workers.emplace_back(
            [this] {
               for (;;) {
                  std::function
         
          task; { std::unique_lock
          
           lock(queue_mutex); condition.wait(lock, [this]() { return stopped || !tasks.empty(); }); if (stopped && tasks.empty()) return; task = std::move(tasks.front()); tasks.pop(); } task(); } } ); } } template 
           
            auto enqueue(F&& f, Args&&... args) -> std::future
            
             ::type> { using return_type = typename std::result_of
             
              ::type; auto task = std::make_shared
              
               
                >(std::bind(std::forward
                
                 (f), std::forward
                 
                  (args)...)); std::future
                  
                   res = task->get_future(); { std::unique_lock
                   
                    lock(queue_mutex); if (stopped) throw std::runtime_error("enqueue on stopped ThreadPool"); tasks.emplace([task]() { (*task)(); }); } condition.notify_one(); return res; } ~ThreadPool() { { std::unique_lock
                    
                     lock(queue_mutex); stopped = true; } condition.notify_all(); for (std::thread& worker : workers) worker.join(); } private: std::vector
                     
                      workers; std::queue
                      
                       
                        > tasks; std::mutex queue_mutex; std::condition_variable condition; bool stopped; }; int main() { ThreadPool pool(4); std::vector
                        
                         
                          > results; for (int i = 0; i < 8; ++i) { results.emplace_back( pool.enqueue([](int i) -> int { std::cout << "task " << i << " running" << std::endl; return i * i; }, i) ); } for (std::future
                          
                           & result : results) { std::cout << "result = " << result.get() << std::endl; } return 0; }
                          
                         
                        
                       
                      
                     
                    
                   
                  
                 
                
               
              
             
            
           
          
         
        
       
      
     
    
在上面的示例中,我们定义了一个ThreadPool类,它维护了一组线程和一个任务队列。线程每次从任务队列中取出一个任务执行,直到任务队列为空或者被告知停止。ThreadPool类提供了enqueue()函数来向任务队列中添加任务,enqueue()函数返回一个std::future对象,用于获取任务的返回值。

结论

本文介绍了C++11中多线程编程的各种方面,包括并发编程、异步任务、原子操作、锁机制以及线程池等。多线程编程是一项复杂的任务,需要仔细考虑应用程序的数据共享和线程同步问题。通过了解C++11多线程编程提供的工具和技术,可以使多线程编程更加容易和高效。