C++11多线程编程(2)

目录

  1. 多线程创建和参数传递
  2. 多线程条件竞争及其解决方法
  3. 多线程事件处理、条件变量
  4. 多线程返回值
  5. 线程池

3.1 多线程事件处理

在第1部分中对多线程的创建、参数传递的使用作了说明,另外对线程间数据共享导致竞争条件异常进行了分析,并给出互斥锁的解决方式。我继续参考thisPointer的教程,深入多线程的事件处理。
什么是多线程的事件处理?有时候,线程往往需要等待类似于条件为真或另一个线程任务完成的事件,这就是多线程的事件处理。先来看一个程序需求。

3.1.1 功能分析

现在我们需要实现一个网络编程的客户端程序,这个程序的任务如下:

  1. 连接服务器
  2. 从XML文件加载数据
  3. 发送加载的数据

分析易知任务1与其他两个任务是独立的,但任务3依赖于任务2。任务1和任务2可以设计在两个平行执行的线程上实现,从而提升程序的性能。
下面我们将使用多线程的方式设计客户端程序。这个程序包含两个线程,线程1需要完成的任务是:

  • 连接服务器
  • 等待线程2加载XML文件的数据
  • 发送加载的数据

线程2需要完成的任务是:

  • 从XML文件中加载数据
  • 通知其他等待该数据的线程

demo

3.1.2 方式1:共享变量

定义一个默认值为false的全局布尔变量isDataLoaded,在Thread 2中设置为真,Thread 1中轮询该布尔变量是否为true,若为true则执行发送数据。显然,需要一个互斥锁来保证isDataLoaded的同步。

#include<iostream>
#include<thread>
#include<mutex>

class Application
{
 std::mutex m_mutex;
 bool m_bDataLoaded;
public:
 Application()
 {
 m_bDataLoaded = false;
 }
 void loadData()
 {
 // Make This Thread sleep for 1 Second
 std::this_thread::sleep_for(std::chrono::milliseconds(1000));
 std::cout<<"Loading Data from XML"<<std::endl;

 // Lock The Data structure
 std::lock_guard<std::mutex> guard(m_mutex);

 // Set the flag to true, means data is loaded
 m_bDataLoaded = true;

 }
 void mainTask()
 {
 std::cout<<"Do Some Handshaking"<<std::endl;

 // Acquire the Lock
 m_mutex.lock();
 // Check if flag is set to true or not
 while(m_bDataLoaded != true)
 {
  // Release the lock
  m_mutex.unlock();
  //sleep for 100 milli seconds
  std::this_thread::sleep_for(std::chrono::milliseconds(100));
  // Acquire the lock
  m_mutex.lock();
  }
  // Release the lock
  m_mutex.unlock();
  //Doc processing on loaded Data
  std::cout<<"Do Processing On loaded Data"<<std::endl;
 }
};

int main()
{
  Application app;

  std::thread thread_1(&Application::mainTask, &app);
  std::thread thread_2(&Application::loadData, &app);

  thread_2.join();
  thread_1.join();
  return 0;
}

方式1的实现存在一个很严重的问题就是Thread 1 在轮询isDataLoaded时需要占用CPU的资源。另外,程序功能越复杂时,需要共享的全局变量就可能越多,就越容易产生Bugs。最好的方式是Thread 2完成加载数据的任务后通知Thread 1,数据准备好了,可以发送了。那么这这么实现呢?

3.1.3 方式2:条件变量

头文件#include<condition_variable> 条件变量也是需要互斥变量来配合使用。

#include <iostream>
#include <thread>
#include <functional>
#include <mutex>
#include <condition_variable>
//using namespace std::placeholders;
class Application
{
    std::mutex m_mutex;
    std::condition_variable m_condVar;
    bool m_bDataLoaded;
public:
    Application()
    {
        m_bDataLoaded = false;
    }
    void loadData()
    {
        // Make This Thread sleep for 1 Second
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        std::cout<<"Loading Data from XML"<<std::endl;
        // Lock The Data structure
        std::lock_guard<std::mutex> guard(m_mutex);
        // Set the flag to true, means data is loaded
        m_bDataLoaded = true;
        // Notify the condition variable
        m_condVar.notify_one();
    }
    bool isDataLoaded()
    {
        return m_bDataLoaded;
    }
    void mainTask()
    {
        std::cout<<"Do Some Handshaking"<<std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(3000));
        // Acquire the lock
        std::unique_lock<std::mutex> mlock(m_mutex);
        // Start waiting for the Condition Variable to get signaled
        // Wait() will internally release the lock and make the thread to block
        // As soon as condition variable get signaled, resume the thread and
        // again acquire the lock. Then check if condition is met or not
        // If condition is met then continue else again go in wait.
        m_condVar.wait(mlock, std::bind(&Application::isDataLoaded, this));
        std::cout<<"Do Processing On loaded Data"<<std::endl;
    }
};
int main()
{
    Application app;
    std::thread thread_1(&Application::mainTask, &app);
    std::thread thread_2(&Application::loadData, &app);
    thread_2.join();
    thread_1.join();
    return 0;
}

4.1 多线程返回值std::future

在许多场景下,我们想从线程的处理中返回需要的值。接下来还是通过一个假设的场景来说明std::future的使用方法。

假设在我们的程序中,一个线程的任务是压缩指定文件夹,我们需要这个线程返回压缩后的文件名和文件大小。
我们先来看看按照前面教程的内容怎么来说实现它。

4.1.1 常规的方法:使用指针共享线程间数据

传递一个指针到任务线程,通过它来保存数据。主线程使用条件变量等待指针指向的值是否改变。当任务线程设置好数据好,主线程将会唤醒并从指针中获取到数据。这种常规的方法,我们需要使用的一个条件变量、一个互斥锁和一个指针变量。3个变量去一个获取线程返回值。若我们需要获取多个返回值呢?程序设计的复杂度越来越大了。有没有简单的方式?

4.1.2 C++11的方法:std::future和std::promise

std::future是一个模版类,它的实例用于保存”未来”值。换句话说,std::future的实例保存的值会在未来设置。另外,std::future提供了一个获取这个”未来”值的成员方法get()。若这个”未来”值还没有被设置,线程调用get()去获取值时将会被阻塞,直到这个值被设置了。

std::promise也是一个模版类,它的实例用于许诺说未来一定会设置一个”未来”值,它是与std::future的实例结合使用的。也即是说std::promise实例与结合的std::future实例共享数据。

下面一步一步来说明它们俩是如何结合使用的。

首先,在Thread 1创建一个std::promise实例

std::promise<int> promiseObj;

现在promiseObj没有设置好值,但它许诺将来别人会设置某个值,一旦设置完成,我们就可以通过关联std::future实例来获取到。
假设Thread 1传递这个promiseObj给Thread 2,那么Thread 1怎么知道Thread 2 设置好值了呢?
很简单,通过关联的std::future实例。每一个std::promise实例都有一个相关联的std::future实例。 在promiseObj传递给Thread 2之前时,保存std::future实例。

std::future<int> futureObj = promiseObj.get_future();

然后Thread 1就可以使用futureObj来获取Thread 2返回的值了。

int val = futureObj.get();

Thread 1会一直阻塞,直到Thread 2 设置了promiseObj的值。

promiseObj.set_value(45);

promise

#include <iostream>
#include <thread>
#include <future>

void initiazer(std::promise<int> * promObj)
{
    std::cout<<"Inside Thread"<<std::endl;     promObj->set_value(35);
}

int main()
{
    std::promise<int> promiseObj;
    std::future<int> futureObj = promiseObj.get_future();
    std::thread th(initiazer, &promiseObj);
    std::cout<<futureObj.get()<<std::endl;
    th.join();
    return 0;
}

有一种情况是”promObj”在没有设置值之前就已经被销毁了,那么调用get()这个方法会抛出异常。

回到这一部分刚开始提出的问题,如果要从线程返回多个值怎么办。现在很简单就能解决了,你需要返回多少个值就传递多少个std::promise实例给其他线程。

参考资料

1.thisPointer多线程