C++11多线程并发中的std::thread、std::mutex和std::future

C++11 新标准中引入了五个头文件来支持多线程编程:,,,

: 该文件主要申明了俩个类,std::atomic 和 std::atomic_flag,另外还声明了一套 C 风格的原子类和与C兼容的原子操作的函数。
: 该文件主要声明了 std::thread 类,另外std::thread 命名空间也在该头文件中。
: 该头文件主要声明了与互斥量(mutex)相关的类,包括 std::mutex 系列类,std::lock_guard, std::unique_lock,以及其他的类型和函数。
:该头文件主要声明了与条件变量相关的类,包括 std::condition_variable 和 std::condition_variable_any。
:该头文件主要声明了 std::promise, std::package_task 两个 Provider 类,以及 std::future 和 std::shared_future 两个 Future 类,另外还有一些与之相关的类型和函数,std::async() 函数就声明在此头文件中。

一. 类std::thread

1.默认构造函数:thread() noexcept; 构造一个任何线程都不执行的线程对象,即空的thread执行对象。
2.初始化函数:

template 
explicit thread (Fn&& fn, Args&&... args);

构造一个线程对象,可以开启线程执行,该thread对象可被joinable,新执行的线程调用函数fn,并传递args作为参数:
fn:可以指向函数,指向成员,或是移动构造函数
args:传递给fn的参数,这些参数可以移动赋值构造。如果fn是一个成员指针,那么第一个args参数就必须是一个对象,或是引用,或是指向该对象的指针。

3.拷贝构造函数 (被禁用):thread (const thread&) = delete,意味着thread不可被拷贝构造。
4.move构造函数 (移动构造函数):thread (thread && x)noexcept 调用成功后x不代表任何thread执行对象。

std::thread::join
该函数返回时,线程执行完成。
当 一个 thread 调用Join方法的时候,MainThread 就被停止执行,直到该 thread 线程执行完毕。

注意:可被joinable的thread对象必须在他们销毁之前被主线程join或者将其设置为detached。

#include     
#include          // std::thread, std::this_thread::sleep_for
#include          // std::chrono::seconds

void pause_thread(int n) 
{
  std::this_thread::sleep_for (std::chrono::seconds(n));
  std::cout << "pause of " << n << " seconds ended\n";
}

int main() 
{
  std::cout << "Spawning 3 threads...\n";
  std::thread t1 (pause_thread,1); 
  std::thread t2 (pause_thread,2);
  std::thread t3 (pause_thread,3);
  std::cout << "Done spawning threads. Now waiting for them to join:\n";
  t1.join();  //停下主线程,进入t1 线程
  t2.join();  //进入t2线程
  t3.join();  // 进入t3线程
  std::cout << "All threads joined!\n";

  return 0;
}

std::thread::detach
分离线程的对象,使他们彼此独立地执行所表示的线程。这俩个线程既没有被阻止,也没有以任何方式同步。当任一结束执行,其资源被释放。

#include        // std::cout
#include          // std::thread, std::this_thread::sleep_for
#include          // std::chrono::seconds

void pause_thread(int n) 
{
  std::this_thread::sleep_for (std::chrono::seconds(n));
  std::cout << "pause of " << n << " seconds ended\n";
}

int main() 
{
  std::cout << "Spawning and detaching 3 threads...\n";
  std::thread (pause_thread,1).detach();
  std::thread (pause_thread,2).detach();
  std::thread (pause_thread,3).detach();
  std::cout << "Done spawning threads.\n";

  std::cout << "(the main thread will now pause for 5 seconds)\n";
  // give the detached threads time to finish (but not guaranteed!):
  pause_thread(5);
  return 0;
}
//输出
Output (after 5 seconds):
Spawning and detaching 3 threads...
Done spawning threads.
(the main thread will now pause for 5 seconds)
pause of 1 seconds ended
pause of 2 seconds ended
pause of 3 seconds ended
pause of 5 seconds ended

更多的thread中的join()和detach()例子。
是否一定要加join()或者detach()呢?其实是不一定的! 看下面程序:

#include
#include
#include 
using namespace std;
class Test{
public:
  void run(int num);
};
void Test::run(int num){
      int count=100;
      while(count>0){
            std::cout<<"aaaa: "<<num<<std::endl;
            count--;
        }
 }
class Test2{
 private:
    Test* testClass;
 public:
    int printNum(int inputNum){
       cout <<"Test2:"<<inputNum<<endl;
    }
    void setTestClass(Test* testClassInput){
     testClass = testClassInput;
  }
};
class System{
 private:
    Test2* pTest2;
    Test* pTest;
 public:
    System(){
      //在主线程运行
       pTest2 = new Test2();
       //开劈新的线程
       pTest = new Test();
       std::thread* ptTest = new thread(&Test::run, pTest,1);
       pTest2->setTestClass(pTest);
  }
  void printNumViaTest2(int num){
       pTest2->printNum(num);
  }
};
int main (){
  System test; 
  for(int i=0; i<1000; i++){
      test.printNumViaTest2(i);
  }
  //usleep(1000);
  return 0;
}

在上述程序中,没有调用join() 或者 detach() 函数,但是该程序可以正常运行,主要是因为线程在运行的时候,主进程还未退出。有时候,若不用join() 或者 detach() 可能会出现terminate called without an active exception Aborted的错误,这是因为线程还在运行的时候,主进程就退出了。

初始化 thread 类的构造函数
对于类的成员函数,我们需要给出类对象的地址:

#include 
#include 

using namespace std;
class A
{
public:
  void fun(int a,int b)
  {
    std::cout<<"this is a thread"<

std::thread(&A::fun,a,k,k+1); 这个地方就可以看出thread 类的构造对于成员函数的重载了,std::thread t(函数(成员函数)地址,对象地址,成员函数的参数1,参数2,参数3...)。
相比非成员函数,成员函数需要给出类实例化对象的地址,如果该线程是在同一类的某一成员函数当中被构造,则直接用this 关键字代替即可。

二. std::mutex

1.Mutex系列类:

std::mutex,最基本的Mutex类
std::recursive_mutex,递归Mutex类
std::time_mutex,定时Mutex类
std::recursive_timed_mutex,定时递归Mutex类

2.Lock类

std::lock_guard,与Mutex RAII相关,方便线程对互斥量上锁。
std::unique_lock,与Mutex RAII相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。

3.函数

std::try_lock,尝试同时对多个互斥量上锁。
std::lock,可以同时对多个互斥量上锁。
std::call_one,如果多个线程需要同时调用某个函数,call_once可以保证多个线程对该函数只调用一次。

std::mutex 介绍

std::mutex 是C++11中最基本的互斥量,std::mutex对象提供了独占所有权的特性—即不支持递归地对std::mutex对象上锁,而std::recursize_lock则可以递归地对互斥量对象上锁。

std::mutex 成员函数

1.构造函数,std::mutex不允许拷贝构造,也不允许move拷贝,最初产生的mutex对象是unlocked状态的。
2.lock(), 调用线程将锁住该互斥量。线程调用该函数会发生下面3种情况: (1) 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用unlock之前,该线程一直拥有该锁。(2) 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞。(3) 如果当前互斥量被当前调用线程锁住,则会产生死锁,故lock()后一般需要 unlock()。
3.unlock(), 解锁,释放对互斥量所有权。
4.try_lock(), 尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被堵塞。出现3种情况:(1) 如果当前互斥量没有被其他锁住,则锁住该互斥量。 (2) 如果当前互斥量被其他线程锁住,则当前调用线程返回false,不会堵塞。(3)如果被当前线程锁住了但没有释放,产生死锁。

加锁和解锁问题
因为我们创造的每一个线程只要在一个进程内,都是共享内存池的,这样在读写数据可能会发生混乱。
C++11提供了mutex类进行加锁和解锁。

#include 
#include 
#include 
std::mutex mut;
class A{
public:
    volatile int temp;
    A(){
        temp=0;
    }
    void fun(int num){
        int count=10;
        while(count>0){
           mut.lock();
            temp++;
            std::cout<<"thread_"<

std::recursive_mutex 介绍
std::recursive_mutexstd::mutex 一样,也是一种可以被上锁的对象,但是和 std::mutex 不同的是,std::recursive_mutex 允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,std::recursive_mutex 释放互斥量时需要调用与该锁层次深度相同次数的 unlock(),可理解为 lock() 次数和 unlock() 次数相同,除此之外,std::recursive_mutex 的特性和 std::mutex 大致相同。

std::time_mutex 介绍
std::time_mutexstd::mutex多了俩个成员函数,try_lock_for()try_lock_until()
try_lock_for函数接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住(与std::mutextry_lock()不同,try_lock如果被调用时没有获得锁则直接返回false),如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回false。

try_lock_until 函数则接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false

#include        // std::cout
#include          // std::chrono::milliseconds
#include          // std::thread
#include           // std::timed_mutex
std::timed_mutex mtx;
void fireworks() {
  //等待加锁: each thread prints "-" every 200ms:
  while (!mtx.try_lock_for(std::chrono::milliseconds(200))) {
    std::cout << "-";
  }
  // 获得锁! - wait for 1s, then this thread prints "*"
  std::this_thread::sleep_for(std::chrono::milliseconds(1000));
  std::cout << "*\n";
  mtx.unlock();
}
int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(fireworks);
  for (auto& th : threads) th.join();
  return 0;
}

std::lock_guard 介绍

#include        // std::cout
#include          // std::thread
#include           // std::mutex, std::lock_guard
#include       // std::logic_error
std::mutex mtx;
void print_even (int x) {
    if (x%2==0) std::cout << x << " is even\n";
    else throw (std::logic_error("not even"));
}
void print_thread_id (int id) {
    try {
        // using a local lock_guard to lock mtx guarantees unlocking on destruction / exception:
        std::lock_guard lck (mtx);
        print_even(id);
    }
    catch (std::logic_error&) {
        std::cout << "[exception caught]\n";
    }
}
int main (){
    std::thread threads[10];
    // spawn 10 threads:
    for (int i=0; i<10; ++i)
        threads[i] = std::thread(print_thread_id,i+1);
    for (auto& th : threads) th.join();
    return 0;
}

std::unique_lock 介绍
与 Mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。

#include        // std::cout
#include          // std::thread
#include           // std::mutex, std::unique_lock

std::mutex mtx;           // mutex for critical section

void print_block (int n, char c) {
    // critical section (exclusive access to std::cout signaled by lifetime of lck):
    std::unique_lock lck (mtx); //在lck的生命周期内对mtx尝试上锁,lck生命周期结束时自动解锁
    for (int i=0; i

三. std::condition_variable

使用条件变量来实现生产者/消费者模型:
一种重要的模型,基于等待/通知机制。生产者/消费者模型描述的是有一块缓冲区作为仓库,生产者可将产品放入仓库,消费者可以从仓库中取出产品,生产者/消费者模型关注的是以下几个点:

  • 生产者生产的时候消费者不能消费
  • 消费者消费的时候生产者不能生产
  • 缓冲区空时消费者不能消费
  • 缓冲区满时生产者不能生产
    C++11多线程并发中的std::thread、std::mutex和std::future_第1张图片

一共有三种关系:生产者与生产者的互斥关系,消费者与消费者的互斥关系,生产者与消费者的互斥且同步关系。

std::condition_variable是条件变量,当std::condition_variable对象的某个wait函数被调用的时候,它使用std::unique_lock(通过std::mutex)来锁住当前线程。当前线程会一直被堵塞,直到另一个线程在相同的std::condition_variable对象上调用了notification(有notify_all()notify_one())函数来唤醒当前线程。

在消费者生产者模型中,如果消费者法线队列中没有东西,就可以让自己休眠,但是不能一直不干活呀,通过notify_one()就是唤醒处于wait中一个变量或者通过判断队列是否为空来实现自身唤醒。

这里解释下为啥在管理互斥锁的时候,使用的是std::unique_lock而不是std::lock_guard,事实上也不能使用std::lock_guard,这里需要解释下wait()函数所做的事情。可以看到,在wait()函数之前,使用互斥锁保护了,如果wait的时候什么都不做,会一直持有互斥锁,那么不能够将数据放入队列中 (看例子2)。所以,wait()函数会先调用互斥锁的unlock()函数,然后再将自己睡眠,在被唤醒后,又会继续持有锁,保护后面的队列操作。而lock_guard没有lockunlock接口,而unique_lock提供了。这就是必须使用unique_lock的原因。

例子1如下:

#include                 // std::cout
#include                 // std::thread
#include                 // std::mutex, std::unique_lock
#include     // std::condition_variable

std::mutex mtx; // 全局互斥锁.
std::condition_variable cv; // 全局条件变量.
bool ready = false; // 全局标志位.

void do_print_id(int id)
{
    std::unique_lock <std::mutex> lck(mtx);
    while (!ready) // 如果标志位不为 true, 则等待...
        cv.wait(lck); // 当前线程被阻塞, 当全局标志位变为 true 之后,
    // 线程被唤醒, 继续往下执行打印线程编号id.
    std::cout << "thread " << id << '\n';
}

void go()
{
    std::unique_lock <std::mutex> lck(mtx);
    ready = true; // 设置全局标志位为 true.
    cv.notify_all(); // 唤醒所有线程.
}

int main()
{
    std::thread threads[10];
    // spawn 10 threads:
    for (int i = 0; i < 10; ++i)
        threads[i] = std::thread(do_print_id, i);

    std::cout << "10 threads ready to race...\n";
    go(); // go!

  for (auto & th:threads)
        th.join();

    return 0;
}

执行结果如下:

threads ready to race...
thread 1
thread 0
thread 2
thread 3
thread 4
thread 5
thread 6
thread 7
thread 8
thread 9

在例子1中使用了cv.wait(lck)的写法;换一种写法,wait()的第二个参数可以传入一个函数表示检查条件,这里使用lambda函数最为简单,如果这个函数的返回是truewait()函数不会被阻塞会直接返回,如果这个函数返回的是falsewait()函数就会堵塞着等待唤醒,如果被伪唤醒,会继续判断函数返回值。
例子2如下:

#include 
#include 
#include 
#include 
#include 

std::deque<int> q;
std::mutex mu;
std::condition_variable cond;

void producer() {
    int count = 10;
    while (count > 0) {
        std::unique_lock<std::mutex> locker(mu);
        q.push_front(count);
        locker.unlock();
        cond.notify_one();  // Notify one waiting thread, if there is one.
        std::this_thread::sleep_for(std::chrono::seconds(1));
        count--;
    }
}

void customer() {
    int data = 0;
    while ( data != 1) {
        std::unique_lock<std::mutex> locker(mu);
        //lambda语法
        cond.wait(locker,[&]{return !q.empty();}); // Unlock mu and wait to be notified
        data = q.back();
        q.pop_back();
        locker.unlock();
        std::cout << "t2 got a value from t1: " << data << std::endl;
    }
}
int main() {
    std::thread t1(producer);
    std::thread t2(customer);
    t1.join();
    t2.join();
    return 0;
}

执行结果如下:

t2 got a value from t1: 10
t2 got a value from t1: 9
t2 got a value from t1: 8
t2 got a value from t1: 7
t2 got a value from t1: 6
t2 got a value from t1: 5
t2 got a value from t1: 4
t2 got a value from t1: 3
t2 got a value from t1: 2
t2 got a value from t1: 1

四. std::future

std::future 可以用来获取异步任务的结果,因此可以把它当成一种简单的线程间同步手段。std::future通常由某个Provider创建,可以把Provider想象成一个异步任务的提供者,Provider在某个线程中设置共享状态的值,与该共享状态相关联的std::future对象调用get (通常在另外一个线程中) 获取该值,如果共享状态的标志不为ready,则调用std::future::get会阻塞当前的调用者,知道Provider设置了共享状态的值(此时共享状态的标志变为ready),std::future::get 返回异步任务的值或异常(如果发生了异常)。

一个有效(valid)的std::future对象只有在有效(valid)的情况下才有用,由std::future默认构造函数创建的future对象不是有效的(除非当前非有效的future对象被move赋值另一个有效的future对象)。

在一个有效的std::future对象上调用get会阻塞当前的调用者,直到Provider设置了共享状态的值或异常(此时共享状态的标志变为ready),std::future::get将返回异步任务的值或异常(如果发生了异常)。std::future对象通常由以下三种Provider创建,并和某个共享状态相关联。Provider可以是函数或者类,分别是:

std::async函数
std::promise::get_future, get_future为promise类的成员函数。
std::packaged_task::get_future,  此时get_future为packaged_task的成员函数

以下主要讲下由async创建的std::future:

1. std::async和std::future
std::async 创建一个后台线程执行传递的任务,这个任务只要是callable object均可,然后返回一个std::futurestd::future存储一个多线程共享的状态,当调用future.get()是会阻塞直到绑定的task执行完毕。
Example 1:

#include 
#include 
void task(){
  for(int i=0; i<10; i++){
    std::cout << "A";
  }
}
int main(){
  //创建异步任务,任务返回值保存在std::future中
  std::future<void> result{std::async(task)};
  //执行其他任务
  for(int i=0;i<10;i++){
    std::cout << "B";
  }
  for(int j=0;j<10;j++){
    std::cout<<"C";
  }
  //需要异步任务结果的时候,如果没有返回,那么会等待至返回,get操作会阻塞
  result.get();
  system("pause");
  return 0;
}

输出:

BBBBBBBBBBCCCCCCCCCCAAAAAAAAAA

Example 2:

#include 
#include 
void task(){
  for(int i=0; i<10; i++){
    std::cout << "A";
  }
}
int main(){
  std::future<void> result{std::async(task)};
  result.get();
  for(int i=0;i<10;i++){
    std::cout << "B";
  }
  system("pause");
  return 0;
}

输出:

AAAAAAAAAABBBBBBBBBB

2. std::launch::async
上面task返回void,这个结果没有用,我们只是单纯的想等待任务线程结束。
对这种需要还可以用更简单的方法:指定一个launch policy

Example 1:

#include 
#include 
void task(){
  for(int i=0; i<10; i++){
    std::cout << "A";
  }
}
int main(){
  std::future<void> result{std::async(std::launch::async, task)};
  for(int i=0;i<10;i++){
    std::cout << "B";
  }
  system("pause");
  return 0;
}

输出

BBBBBBBBBBAAAAAAAAAA

在创建async的时指定一个lauch policy,连result.get都可以不用了,不过还是需要把async的返回值赋给result。

Example 2:

#include 
#include 
void task(){
  for(int i=0; i<10; i++){
    std::cout << "A";
  }
}
int main(){
  std::future<void> result{std::async(std::launch::async, task)};
  result.get();
  for(int i=0;i<10;i++){
    std::cout << "B";
  }
  system("pause");
  return 0;
}

输出

AAAAAAAAAABBBBBBBBBB

3.std::launch::deferred

总共有倆种launch policy:

  1. std::launch::async 当返回的future失效前会强制执行task,即不调用future.get也会保证task的执行
  2. std::launch::deferred 仅当调用future.get时候才会执行task, 如果创建async时不指定launch policy,会默认std::launch::async | std::launch::deferred,根据情况选一种执行

Example 1:

#include 
#include 

void task() {
    for (int i = 0; i < 10; i++) {
        std::cout << "A";
    }
}

int main() {
    std::future<void> result{ std::async(std::launch::deferred,task) };
    for (int i = 0; i < 10; i++) {
        std::cout << "B";
    }
    result.get(); //加了.get()
    system("pause");
    return 0;
}

输出:

BBBBBBBBBBAAAAAAAAAA

Example 2:

#include 
#include 
void task(){
  for(int i=0; i<10; i++){
    std::cout << "A";
  }
}
**Example 1:**
int main(){
  std::future<void> result{std::async(std::launch::deferred, task)}; //没有调用.get()
  for(int i=0;i<10;i++){
    std::cout << "B";
  }
  system("pause");
  return 0;
}

输出:

BBBBBBBBBB

Another example:

#include
#include
#include
#include
#include
#include
#include
using namespace std;
 
int mythread() //线程入口函数
{
	cout << "mythread start" << "threadid= " << std::this_thread::get_id() << endl; //打印线程id
 
	std::chrono::milliseconds dura(5000); //定一个5秒的时间
	std::this_thread::sleep_for(dura);  //休息一定时常
 
	cout << "mythread end" << "threadid= " << std::this_thread::get_id() << endl; //打印线程id
 
	return 5;
}
int main()
{
	cout << "main" << "threadid= " << std::this_thread::get_id() << endl;
	//std::future result = std::async(mythread);//流程并不卡在这里,让程序决定是否开启新的线程
	std::future<int> result = std::async(std::launch::async, mythread);//开启新的线程,mythread和mainthread的线程id是不同的。
	cout << "continue....." << endl;
 
	//枚举类型
	std::future_status status = result.wait_for(std::chrono::seconds(0));//等待一秒
	
	if (status == std::future_status::deferred)
	{
		//线程被延迟执行了,系统资源紧张
		cout << result.get() << endl; //此时采取调用mythread()
	}
	else if (status == std::future_status::timeout)//
	{
		//超时:表示线程还没执行完;我想等待你1秒,希望你返回,你没有返回,那么 status = timeout
		//线程还没执行完
		cout << "超时:表示线程还没执行完!" << endl;
	}
	else if (status == std::future_status::ready)
	{
		//表示线程成功返回
		cout << "线程成功执行完毕,返回!" << endl;
		cout << result.get() << endl;
	}
 
	cout << "I love China!" << endl;
	return 0;
}

更深入的如std::atomic请移步https://www.cnblogs.com/haippy/p/3284540.html,非常好的C++11并发的版块,也mark一下,以供后面继续学习。

你可能感兴趣的:(C/C++)