并发与多线程

转自:🔥【github】

1、创建线程

调用线程函数:

1
thread myThread(函数名);

可调用对象做参数:

  • 线程的入口函数在对象的类重载()的函数void operator()()中,对象是值传递所以还必须有拷贝构造函数Obj(const &obj),这里对象是值传递
    1
    2
    3
    4
    void operator()(){};
    thread myThread(对象);
    void operator()(int val);
    thread myThread(对象,val);
    lambda表达式:

1
2
3
4
5
6
7
8
9
auto mylamthread = [] {  ;}
````
使用线程:
-----------
* 实际只使用join():只有当所有线程运行结束后才运行主线程
```cpp
threadObj.detach();
threadObj.join();
threadObj.joinable(); //判断是否可以使用join()

2、线程传参

普通类型做线程参数

  • 创建线程时,即使线程函数参数是&,主线程传递也依旧是值传递重新拷贝一份给线程函数。
    1
    2
    void func(int &var,){}    
    thread myThread(func,var); //myTread中var和func中的var不同地址
    类对象做线程参数

  • 传递类对象,应避免隐式类型转换,全部使用构建临时对象,线程函数必须用const &来接,避免再次构造对象。
    1
    2
    void func(const Obj &obj){}     
    thread myThread(func,Obj(0)); //先构造临时对象Obj(0),值传递复制给func函数obj对象
  • 如果非要用主线程的对象本身做线程参数
    1
    2
    3
    4
    5
    void func(Obj &obj)                 //可以不用const

    Obj obj;
    thread myThread(func,std::ref(obj)); //相当于&obj

    用类成员函数指针做线程函数

1
2
void threadWorkFunc(int val){};
thread myThread(&Obj::threadWorkFunc,&obj,val); //使用&obj也可保证主线程和线程使用同一个对象

3、互斥量

mutex类

  • 相当于一把锁。lock()unlock()必须成对使用,先lock,再操作共享数据,然后unlock

lock_guard类模板

  • 为了防止忘记unlock,引入std::lock_guard类模板,在定义时,构造函数中自动调用lock(),在析构函数中自动调用unlock(),直接取代unlock lock函数,不能共用;
  • 使用:只需要在操作共享数据前加一行将互斥量加入模板即可,不需要考虑解锁
  • 一般项目使用lock_guard就足够了
    1
    std::lock_guard<std::mutex> mutexGuard(my_mutex);
    死锁

  • 至少有两个互斥量存在,在两个进程中,两个互斥量的lock()次序不同,就会引起死锁只要保持上锁的顺序一致就行

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Obj{                                                //线程类
private:
std:List<int> MsgRecvQueue; //共享数据容器
mutex my_mutex; //互斥锁
public:
void outMsgRecvQueue(){ //读数据线程函数
my_mutex.lock();
if(!MsgRecvQueue.empty()){ //判断也是操作共享数据
MsgRecvQueue.pop_front(); //if函数的每个分支都要解锁
my_mutex.unlock();
}else{
my_mutex.unlock();
}
};

void inMsgRecvQueue(){ //写数据线程函数
my_mutex.lock(); //锁住
MsgRecvQueue.push_back(); //写数据
my_mutex.unlock(); //解锁
};
};

Obj obj;
std::thread myInMsgThread(&Obj::inMsgRecvQueue,&obj); //写数据线程
std::thread myOutMsgThread(&Obj::outMsgRecvQueue,&obj); //读数据线程
myInMsgThread.join();
myOutMsgThread.join();

4、单例模式与数据共享问题

  • 构造函数私有化 本类指针类型的静态成员变量 返回本类指针得静态成员函数
  • 对象只能创建一次
  • 推荐主线程中创建对象(例如初始化配置信息),多线程只读访问,不需要互斥
  • 线程中创建单例对象需要建立互斥
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Obj{
    private:
    Obj(){};
    private:
    static Obj* obj;
    public:
    static Obj* GetInstance(){
    if(obj==null){ //双重锁定提高效率
    std::lock_guard<std::mutex> mutexGuard(myMutex);
    if(obj==null) //即if判断两次,因为=null时不一定指对象没有new,可能多个线程争抢权限
    obj=new Obj;
    }
    return obj;
    }
    };

    Obj* Obj::obj=null;

std::this_thread::get_id()

5、效率问题

双重锁定

  • 使用两个判断,第一次提高效率,第二次只有加锁后的判断成立,才是真正的obj==bull
    1
    2
    3
    4
    if(obj==null){                 
    std::lock_guard<std::mutex> mutexGuard(myMutex);
    if(obj==null)
    obj=new Obj;

条件变量

  • std::condition_variable 是一个类,函数waite()等待通知notify_noce(),收到通知后,将开启循环尝试拿锁
  • 拿锁成功后,第二参数判断为true:往后执行代码
  • 拿锁成功后,第二参数判断为false:继续休眠,等待notify_noce()通知
    1
    2
    3
    4
    写数据线程1
    std::lock_guard<std::mutex> mutexGuard(myMutex)
    dataQueue.push_back(1);
    my_condition.notyfy_one(); //my_condition是condition_variable类对象
    1
    2
    3
    4
    5
    6
    7
    8
    读数据线程2
    std::lock_guard<std::mutex> mutexGuard(myMutex)
    my_condition(mutexGuard,[this]{ //第二参数使用lambda表达式
    if(!dataQueue.empty())
    return true;
    return false;
    });
    dataQueue.pop_front(); //收到通知,持续拿锁,拿到后判断非空,则读数据