跳转至

《C++并发编程实战》

第二章. 管理线程

c++11起有了新的线程感知内存模型(thread-aware memory model),支持多线程,并通过std::thread等类对多线程进行管理。

这里根据书中第二章内容,先简单介绍std::thread类如何使用。

1. std::thread

头文件

#include <thread>

C++程序main函数启动时,本身就是一个线程,在这个线程中创建std::thread对象(构造时需要传入线程运行的函数),就是启动了另一个线程。

std::thread就是一个对象,只是可以和一个运行中的线程相绑定,持有std::thread对象的线程可以和其绑定的线程进行一定的交互。

1.1 方法列表

cpp-reference

成员方法 - 构造函数 - 析构函数:调用析构时不能还绑定着运行中的线程(析构前就调用joindetach) - 操作符=:只支持移动场景,不支持拷贝

Observers - joinable:判断对象是否绑定着运行中线程,实现是判断get_id() == std::thread::id - get_id:获取绑定中线程的id,如果没有绑定线程,则返回当前线程id,即std::thread::id - native_handle:获取底层handle - hardware_concurrency:当前系统硬件并行度,例如双核+超线程,结果返回4

TODO: 对get_id做进一步说明

Operators - join:只能在joinabletrue时调用,等待绑定中线程运行完成并返回结果,之后和该线程解除绑定(即joinable返回false) - detach:只能在joinabletrue时调用,和解除和线程的绑定关系,此后程序中无法再操作这个线程,也就是把这个线程作为守候线程(daemon thread)运行 - swap:交换两个std::thread对象

1.2 构造

  • 可以构造一个不绑定方法的std::thread,即不传任何参数,此时std::thread不绑定任何运行中线程
  • 传入函数,即新起一个线程,并且把这个线程绑定在std::thread对象上
  • 不支持拷贝构造
  • 可以移动构造,即把另一个std::thread绑定的线程转移到这个std::thread对象上,绑定的线程本身运行不受影响

构造std::thread,可以用正常Function和Args,可以传lambda,可以传重载了operator ()的类。

TODO: 写下几种构造的demo

1.3 析构

如果std::thread对象在销毁前,没有决定被joindetachstd::thread的析构函数中会调用std::terminate。 所以std::thread对象销毁前,必须显式调用joindetach,否则在std::thread的析构函数中会调用std::terminate,中止整个程序。

例如如下代码

#include <iostream>
#include <thread>

int main() {
    auto func = []() {
        std::cout << "Hello World in another thread\n";
    };
    std::cout << "Begins\n";
    {
        std::thread f(func);
    }
    std::this_thread::sleep_for(std::chrono::seconds(3));
    std::cout << "Ends\n";
}

因为f对象没有显式处理,程序运行结果如下,没有输出"Ends"

Begins
libc++abi: terminating
zsh: abort      ./a.out

2. 传递参数给线程函数

TODO: 说明参数传递过去后的作用域,看下什么时候参数会被copy,通过std::ref来指定传递引用,以及lambda能不能更方便实现传递引用(lambda里捕获引用的话)

TODO: 说明std::thread不能直接获取线程结果,可以靠传一个引用进去来写入,也可以靠future机制

2. 互斥锁std::mutex

c++11引入,线程安全,同时只能被一个线程持有

#include <mutex>

2.1 基本操作

  • lock: 锁住该互斥量,如果被其它线程占有,则当前线程被阻塞,如果当前互斥量被当前线程锁住,则会产生死锁
  • unlock: 释放对该互斥量的所有权
  • try_lock: 尝试锁住mutex,如果失败则返回false,不会阻塞

2.2 std::lock_guard

c++11引入

std::mutex提供RAII(Resource Acquisition Is Initialization)形式的封装类

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

int num;
std::mutex m;

void increment() {
    // 进行加锁
    const std::lock_guard<std::mutex> lock(m);
    std::cout << "increment in thread " << std::this_thread::get_id() << ", add num to " << ++num << '\n';
}

int main() {
    std::thread threads[10];
    for (int i = 0; i < 10; i++) {
        threads[i] = std::thread(increment);
    }
    for (int i = 0; i < 10; i++) {
        threads[i].join();
    }
    std::cout << "num " << num << '\n';
}

2.3 std::unique_lock

c++11引入

2.4 std::scoped_lock

c++17引入