1. thread

例子:

int main()
{
    thread t1{func1, "A"};
    thread t2{func2, "B"};
    t1.join();
    t2.join();
}

说明:

  1. t1和t2的第一个参数为线程要执行的内容,第二参数为参数1的参数
  2. thread对象在销毁之前必须join或者detach,如果没有,程序会挂掉
  3. 可以使用类似RAII的方法解决thread不能自动detach的问题。

2. mutex

最简单的互斥锁,用法:

lock_guard<mutex> guard{mtx}; // C++11
lock_guard guard{mtx}; // C++ 17

在guard离开作用域时自动释放锁。

3. shared_mutex

共享读写互斥锁

shared_lock guard{mtx}; // for read
unique_lock guard{mtx}; // for write

4. 使用条件变量和锁实现同步锁

void work(condition_variable &cv, int& result)
{
    this_thread::sleep_for(2s); // 模拟某个耗时的工作
    result = 42; // 工作的结果
    cv.notify_one(); // 通知主线程工作已完成 4
}

int main()
{
    condition_variable cv;
    mutex cv_mut;
    int result;

    scope_thread th{work, ref(cv), ref(result)}; // 1
    cout<<"I am waiting, now\n";
    unique_lock lock{cv_mut}; // 2
    cv.wait(lock);  // 3
    cout << "Answer: "<< result<<"\n";
    return 0;
}

说明:

  1. 起一个线程来做某个任务。work是任务的内容,ref是包装器,用于把参数包装成引用形式
  2. 惯用法,配合condition_variable使用
  3. 等待工作完成
  4. 潜在问题:如果在(3)之前就已经执行了(4)notify,那么它就永远也等不到了。

5. 使用future实现同步锁

例子:

int work() // 1
{
    this_thread::sleep(2s);
    return 42;
}

int main()
{
    auto fut = async(launch::async, work);  // 2
    cout<<"I am waiting, now\n";
    cout << "Answer: "<< result<<"\n";
}

说明:

  1. 以正常函数的方式来写要执行的工作
  2. launch::async表明以异步的方式执行

6. 指令顺序问题的原因

  1. 编译器优化
  2. 处理器乱序

6.1. 例子

初始化状态:

int x = 0, y = 0;

线程1:

x = 1;
y = 2;

线程2:

if(y == 2)
{
    x = 3;
    y = 4;
}

可能的结果是:

  • x=1, y=2
  • x=3, y=4
  • x=1, y=4,因为线程1有可能是先y=2再x=1

6.2. 获取、释放语义

acquire语义:此处是一个原子读操作,且在此后面的所有读操作都不应在此之前执行
release语义:此处是一个原子写操作,且在此前面的所有写操作都不应在此之后执行

还是上面的例子:

初始化状态:

int x = 0;
atomic<int> y = 0;    
}

线程1:

x = 1;   // 1
y.store(2, memory_order_release);   // 2

线程2:

if(y.load(memory_order_acquire) == 2)
{
    x = 3;
    y.store(4, memory_order_relaxed);  // 3
}

说明:

  1. 这种写法保证(1)一定在(2)之前执行
  2. 只需要保证是原子写

7. thread_local

7.1. thread_local作为成员变量的用法

class Obj
{
    public:
        static Obj& get_instance();
    private:
        static thread_local Obj inst_;
};

thread_local Obj obj::inst_; // 每个线程创建时构造inst_

Obj& Obj::get_instance()
{
    return inst_;
}

7.2. thread_local作为普通变量的用法

class Obj
{
    public Obj& get_instance();
};
Obj &Obj::get_instance() // 每个线程第一次调用次函数时创建对象
{
    thread_local Obj inst;
    retun inst;
}

8. 多线程性能优化

  1. 多线程加锁和竞争是性能杀手
  2. 能使用atomic就不要使用mutex,例如count++
  3. 如果读比写多,建议使用shared_mutex代替mutex
  4. 使用thread_local变量

results matching ""

    No results matching ""