C++11终于支持多线程了,喜大普奔。本文主要介绍C++11中多线程的相关的知识和概念,在最后我们将实现一个简单的线程池来巩固相关的知识。

C++11 并发编程

基本使用

在C++11中创建一个线程还是很简单的。

示例代码如下:

#include <iostream>
#include <thread>

// 线程任务
void thread_task()
{
    std::cout << "Doing job in thread..." << std::endl;
}

int main()
{
    // 创建线程
    std::thread t(thread_task);
    // 等待线程结束
    t.join();
    return 0;
}
/*
* $ g++ test_thread.cpp -std=c++11 -lpthread -o test_thread
*/

std::thread 详解

构造函数

默认构造函数:
thread() noexcept;    //创建一个空的thread 执行对象

初始化构造函数:
template <class Fn, class... Args>
explicit thread(Fn&& fn, Args&& args);    //创建一个thread,可joinable, 新线程会调用fn,使用args参数

move构造函数:
thread (thread&& x) noexcept; //调用成功后,x 不代表任何 thread 执行对象

示例如下

#include <iostream>
#include <thread>
#include <chrono>

void f1(int n)
{
    for (int i = 0; i < 5; ++i) {
        std::cout << "Thread 1 is executing..." << std::endl;
        ++n;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    std::cout << "result for thread 1: " << n << std::endl;
}

void f2(int& n)
{
    for (int i = 0; i < 5; ++i) {
        std::cout << "thread 2 is excuting..." << std::endl;
        ++n;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    std::cout << "result for thread 2: " << n << std::endl;
}

int main()
{
    int n = 0;
    std::thread t1;  // t1 is not a thread
    std::thread t2(f1, n+1);    // pass by value
    std::thread t3(f2, std::ref(n));    // pass by reference
    std::thread t4(std::move(t3));  // construct by move, after this, t3 is no longer a thread
    t2.join();
    t4.join();
    std::cout << "result is: " << n << std::endl;
    return 0;
}

其他成员函数

  • get_id 说明: 获取线程ID 定义: id get_id() const noexcept;
    用法: std::thread::id mainId = std::this_thread::get_id();

  • joinable 说明: 判断是否joinable 定义: bool joinable(); 用法: std::thread t; t.joinable();

  • join 说明: 等待一个线程 定义: 用法:

  • detach 说明: 定义: 用法:

  • swap 说明: 定义: 用法:

  • native_handle 说明: 定义: 用法:

  • hardware_concurrency 说明: 定义: 用法:

mutex 详解

mutex 又称互斥量,其定义与相关函数声明均定义于<mutex>头文件中。

mutex 类型

  • std::mutex: 最基本的mutex类

  • std::recursive_mutex: 递归mutex类

    • 允许同一个线程对互斥量多次上锁,即递归上锁,来获得对互斥量对象的多层所有权。
    • unlock()次数应与lock()次数一致
  • std::timed_mutex: 定时的mutex类

    • 比std::mutex 多两个成员函数: try_lock_for(), try_lock_util()
    • try_lock_for(const chrono::duration& rel_time):接受一个时间参数,表示在这段时间内如果没有获得锁则返回false,与try_lock()直接返回false不同。
    • try_lock_untile(const chrono::time_point& abs_time): 接受一个时间参数,表示在指定时间点未到来之前,线程如果没有获得锁,则返回false。
  • std::recursive_timed_mutex: 定时递归的mutex类

    • 其特性从 std::recursive_mutex 和 std::recursive_timed_mutex 推出。

mutex 成员函数

  • 构造函数

    std::mutex 不允许 copy 构造,也不允许 move构造,初始化后的mutex对象均处于unlocked状态

  • lock()

    锁定互斥量,直到调用 unlock()之前,线程一直拥有该锁。 线程调用该函数时,分为一下三种情况:

    • 互斥量没有被锁: 线程锁住互斥量,直至调用unlock()
    • 互斥量被其他线程占用: 当前线程被阻塞
    • 互斥量被当前线程锁住: 产生死锁
  • unlock()

    解锁,释放对互斥量的所有权

  • try_lock()

    尝试锁住互斥量,分为一下三种情况:

    • 互斥量没有被其他线程占用: 当前线程锁住互斥量,直至调用 unlock
    • 互斥量被其他线程占用: 返回false,不会导致阻塞
    • 互斥量被当前线程锁住: 产生死锁

使用示例:

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex

/**
 * 当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明.
 * 该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。
 * volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值。
 **/
volatile int counter1 = 0;
volatile int counter2 = 0;

std::mutex mtx1;    // locks access to counter1
std::mutex mtx2;    // locks access to counter2
std::timed_mutex mtx3; // timed mutex

// use try lock
void try_lock_10k_increases() {
    for (int i = 0; i < 10000; ++i) {
        if (mtx1.try_lock()) {   // only increase if currently not locked:
            counter1++;
            mtx1.unlock();
        }
    }
}

// use lock
void lock_10k_increase()
{
    for (int i = 0; i < 10000; i++){
        mtx2.lock();
        counter2++;
        mtx2.unlock();
    }
}

// use try_lock_for
void fire()
{
    // waitting to get a lock, each thread prints "-" every 20ms
    while (!mtx3.try_lock_for(std::chrono::milliseconds(200)))
        std::cout << "-";

    // get a lock, wait for 1s and print "*"
    std::this_thread::sleep_for(std::chrono::microseconds(100000));
    std::cout << "*" << std::endl;
    mtx3.unlock();
}

int main (int argc, const char* argv[]) 
{
    std::thread threads[30];
    for (int i=0; i<10; ++i)
        threads[i] = std::thread(try_lock_10k_increases);

    for (int i=10; i<20; ++i)
        threads[i] = std::thread(lock_10k_increase);

    for(int i=20; i<30; i++)
        threads[i] = std::thread(fire);

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

    std::cout << "counter1: " << counter1 << std::endl;
    std::cout << "counter2: " << counter2 << std::endl;

    return 0;
}

/*
编译运行:
$ g++ test_mutex.cpp -std=c++11 -lpthread -o test_mutex
$ ./test_mutex
*
--------*
*
------*
*
----*
*
--*
*
*
counter1: 53448
counter2: 100000
counter2每次结果均为100000, counter1每次执行结果都不一致。
*/

lock 详解

C++11 提供了两种基本的锁类型:

  • std::lock_guard: 方便线程对互斥量上锁。当一个lock_guard()对象被创建时,它会尝试赋予对互斥体的所有权,当控制离开lock_guard的对象范围的时候,lock_guard()释放互斥锁。

  • std::unique_lock: 方便线程对互斥量上锁,提供了更好的上锁何解锁控制。

以及相关的锁tag:

  • std::adopt_lock: 假设线程已经拥有互斥量的所有权
  • std::defer_lock: 不要求互斥量的所有权
  • std::try_to_lock: 尝试获取互斥量的所有权
#include <mutex>
#include <thread>
#include <iostream>

struct bank_account {
    explicit bank_account(int balance) : balance(balance) {}
    int balance;
    std::mutex m;
};

void transfer(bank_account &from, bank_account &to, int amount)
{
    // lock both mutexes without deadlock
    std::lock(from.m, to.m);
    // make sure both already-locked mutexes are unlocked at the end of scope
    std::lock_guard<std::mutex> lock1(from.m, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(to.m, std::adopt_lock);
    // equivalent approach:
    // std::unique_lock<std::mutex> lock1(from.m, std::defer_lock);
    // std::unique_lock<std::mutex> lock2(to.m, std::defer_lock);
    // std::lock(lock1, lock2);
    from.balance -= amount;
    to.balance += amount;
}

int main()
{
    bank_account my_account(100);
    bank_account your_account(50);

    std::thread t1(transfer, std::ref(my_account), std::ref(your_account), 10);
    std::thread t2(transfer, std::ref(your_account), std::ref(my_account), 5);

    t1.join();
    t2.join();

    std::cout << "my account: " << my_account.balance << std::endl;
    std::cout << "your account: " << your_account.balance << std::endl;

    return 0;
}

lock_guard

unique_guard

results matching ""

    No results matching ""