【C++新特性】C++11新特性之多线程

173 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

std::lock相关

这里主要介绍两种RAII方式的锁封装,可以动态的释放锁资源,防止线程由于编码失误导致一直持有锁。

c++11主要有std::lock_guard和std::unique_lock两种方式,使用方式都类似,如下:

#include <iostream>
#include <mutex>
#include <thread>
#include <chrono>
​
using namespace std;
std::mutex mutex_;
​
int main() {
    auto func1 = [](int k) {
        // std::lock_guard<std::mutex> lock(mutex_);
        std::unique_lock<std::mutex> lock(mutex_);
        for (int i = 0; i < k; ++i) {
            cout << i << " ";
        }
        cout << endl;
    };
    std::thread threads[5];
    for (int i = 0; i < 5; ++i) {
        threads[i] = std::thread(func1, 200);
    }
    for (auto& th : threads) {
        th.join();
    }
    return 0;
}

std::lock_gurad相比于std::unique_lock更加轻量级,少了一些成员函数,std::unique_lock类有unlock函数,可以手动释放锁,所以条件变量都配合std::unique_lock使用,而不是std::lock_guard,因为条件变量在wait时需要有手动释放锁的能力,具体关于条件变量后面会讲到。

std::atomic相关

c++11提供了原子类型std::atomic,理论上这个T可以是任意类型,但是平时只存放整形,别的还真的没用过,整形有这种原子变量已经足够方便,就不需要使用std::mutex来保护该变量啦。看一个计数器的代码:

struct OriginCounter { // 普通的计数器
    int count;
    std::mutex mutex_;
    void add() {
        std::lock_guard<std::mutex> lock(mutex_);
        ++count;
    }
​
    void sub() {
        std::lock_guard<std::mutex> lock(mutex_);
        --count;
    }
​
    int get() {
        std::lock_guard<std::mutex> lock(mutex_);
        return count;
    }
};
​
struct NewCounter { // 使用原子变量的计数器
    std::atomic<int> count;
    void add() {
        ++count;
        // count.store(++count);这种方式也可以
    }
​
    void sub() {
        --count;
        // count.store(--count);
    }
​
    int get() {
        return count.load();
    }
};

是不是使用原子变量更加方便了呢?

std::call_once相关

c++11提供了std::call_once来保证某一函数在多线程环境中只调用一次,它需要配合std::once_flag使用,直接看使用代码:

std::once_flag onceflag;
​
void CallOnce() {
    std::call_once(onceflag, []() {
        cout << "call once" << endl;
    });
}
​
int main() {
    std::thread threads[5];
    for (int i = 0; i < 5; ++i) {
        threads[i] = std::thread(CallOnce);
    }
    for (auto& th : threads) {
        th.join();
    }
    return 0;
}

volatile相关

volatile通常用来建立内存屏障,volatile修饰的变量,编译器对访问该变量的代码通常不再进行优化,看下面代码:

int *p = xxx;
int a = *p;
int b = *p;

a和b都等于p指向的值,一般编译器会对此做优化,把*p的值放入寄存器,就是传说中的工作内存(不是主内存),之后a和b都等于寄存器的值,但是如果中间p地址的值改变,内存上的值改变啦,但a,b还是从寄存器中取的值(不一定,看编译器优化结果),这就不符合需求,所以在此对p加volatile修饰可以避免进行此类优化。

注意:volatile不能解决多线程安全问题,针对特种内存才需要使用volatile,它和atomic的特点如下:• std::atomic用于多线程访问的数据,且不用互斥量,用于并发编程中• volatile用于读写操作不可以被优化掉的内存,用于特种内存中