c++线程专题

逐步更新中~~~,参考书籍《C++并发编程实战(第2版)》,不照搬书,只写理解感悟。

引入头文件

#include <thread>

线程启动

std::thread t(my_func);

若需等待线程执行完毕,才继续之后的代码,用join

if (t.joinable())
{
    t.join();
}

若不等待,可以分离出去(分离出去的线程被称为守护线程daemon thread),此时t.joinable()永远变为false

t.detach();

【线程传参】 

线程具有内部存储空间,参数会先复制到该空间,再被线程使用。即使被&修饰。

若要被真正的引用,使用std::ref()

#include <iostream>
#include <thread>
#include <string>

void f(int i, const std::string& s)
{
    std::cout << i << "," << s << std::endl;
    std::cout << &s << std::endl;
}

int main()
{
    std::string str = "hello";
    std::thread t(f, 3, std::ref(str));    //真正的引用传参
    std::cout << &str << std::endl;
    t.join();
    return 0;
}

 类的非静态成员函数当作线程函数,可以如下方式:

class Camera
{
public:
    void do_work(); //类的非静态成员函数
};

void Camera::do_work()
{
    std::cout << "hello" << std::endl;
}

int main()
{
    Camera* cam=new Camera;
    std::thread t(&Camera::do_work,cam);//等价于调用cam->do_work()
    //若do_work(参数1,参数2)
    //std::thread t(&Camera::do_work, cam, 参数1, 参数2);
    t.join();
    return 0;
}

 【另外一种传参方式——移动语义】

数据从一个对象转移到另一个对象内部,原对象被“搬空”。即参数只能移动不能复制,这就是移动语义。智能指针常常用到移动语义。

原对象虽然被“搬空”,但是其内存空间依然存在,只是其中的数据没了。参考我的博客第27节c++笔记2(参考learncpp.com) - 夕西行 - 博客园 (cnblogs.com)

【移交线程归属权】 

std::thread t1(f1);
std::thread t2 = std::move(t1); //函数f1本来在线程t1中执行,现在移交给线程t2执行。t1不再管控。
//t2 = std::thread(f2);            //t2已经有管控的任务,不允许再管控其他。
//t1 = std::thread(f2);            //t1可以管控f2

if (t1.joinable())                //若t1没有管控任何任务,也就没必要join了,即joinable为false
{
    t1.join();
}
if (t2.joinable())
{
    t2.join();
}

只能移动,不能复制的有:std::thread,std::unique_ptr

void f1(std::thread t);    //普通函数,参数为线程对象
void f2();                //线程函数
std::thread t1(f2);
f1(std::move(t1));        //具名对象,需显式调用move
//f1(std::thread(f2));    //临时对象(非具名对象),自动move
//f1(t1);                //f1参数类型为std::thread&,可以使用此方式
//std::thread t2 = t1;    //错误。只能移动,不能复制。unique_ptr也具有此特性。

 【切分任务】

用多线程切分任务,等所有线程结束后才继续执行之后的内容。可以用vector统一管理thread

关于emplace_back与push_back,参考一文轻松搞懂emplace_back与push_back - 知乎 (zhihu.com)

void f1(int i);
void g()
{
    std::vector<std::thread> vThreads;
    for (int i = 0; i < 20; i++)
    {
        vThreads.emplace_back(f1, i);    //比push_back更强大,emplace_back直接将对象构造在了容器内
    }

    for (auto& entry : vThreads)
    {
        entry.join();
    }
    //...
}

 【线程id】

可以通过std::this_thread::get_id()获取当前线程id,也可以指定id方便区分哪个对象调用了该函数

class Camera
{
public:
    void do_work(int cameraID); //类的非静态成员函数
};

void Camera::do_work(int cameraID)
{
    std::cout << cameraID << std::endl;
}

void g()
{
    Camera* cam1 = new Camera;
    Camera* cam2 = new Camera;
    std::vector<std::thread> vThreads;
    vThreads.emplace_back(&Camera::do_work, cam1, 1);//相机1调用
    vThreads.emplace_back(&Camera::do_work, cam2, 2);//相机2调用

    for (auto& entry : vThreads)
    {
        entry.join();
    }
    //...
}

 【保护共享数据】

当多线程访问的共享数据不是只读时,就需要被保护。

方法一、用互斥保护

所有访问共享数据的代码都标记成互斥。注意是所有、所有、所有。包括指针、引用等访问共享数据的方式,如常用的引用传参。

下述函数中第一行都需加上

std::lock_guard<std::mutex> guard(some_mutex);
#include <mutex>

int protected_data;
std::mutex some_mutex;

int get()                //
{
    return protected_data;
}

void set(int new_value)    //
{
    protected_data = new_value;
}

void add(int& value)    //写,操作protected_data
{
    value++;
}

 防范死锁

当有多个锁时,都在等待对方解锁,一直等不到,就会死锁。可以使用下述方式管理多个锁。

//交换类MyClass的两个对象,为保证互换正确完成,需都加锁。
//一般保证按相同顺序加锁,不会死锁。但有时候无法保证顺序,所以使用下述方式
#include <mutex>

class MyClass {};
void swap(MyClass& obj1, MyClass& obj2);
class X
{
private:
    MyClass m_obj;
    std::mutex m_mutex;
public:
    X(const MyClass& obj):m_obj(obj){}
    friend void swap(X& objX1, X& objX2)
    {
        if (&objX1 == &objX2) return;
        std::lock(objX1.m_mutex, objX2.m_mutex);                            //管理两个锁
        std::lock_guard<std::mutex> guard1(objX1.m_mutex, std::adopt_lock);    //std::adopt_lock表示已经锁上,guard1依据此接收锁的归属权,不得在构造函数内另行加锁
        std::lock_guard<std::mutex> guard2(objX2.m_mutex, std::adopt_lock);
        swap(objX1.m_obj, objX2.m_obj);
    }
};

 

posted @ 2023-11-18 17:15  夕西行  阅读(8)  评论(0编辑  收藏  举报