c++中多线程的用法

并发与多线程

线程并不是越多越好,需要独立的堆栈空间,线程之间的上下文切换要保存很多中间状态。

thread myobj(childThreadfunc); 接收一个可调用对象作为参数

myobj.join() 调用,阻塞主线程,让主线程等待,等子线程执行完了再继续;

myobj.detach(); 主线程不和子线程混合,主线程执行完之后并不影响子线程的执行;主线程执行完毕后,子线程的内容转移到后台执行

调用 detach() 之后不能再 join() 了,可以调用 joinable() 查看子线程是否可以 join()

1、创建多线程的几种方式

传递临时对象作为线程参数; 临时对象就是直接构造的对象 Animal( , , );构造出来的就是临时对象,可以直接在里面构造;

慎用detach;

传递类对象、智能指针作为线程参数;

std::ref(类的实例化对象) // 对于引用传递,用ref包一层,实现真正的引用传递,不会通过拷贝构造重新构造一个对象

void printf(unique_ptr<int> pan){
​
}
​
int main(int argc, char *argv[]) {
•       unique_ptr<int>  myp(new int(100));
•       std::thread myobj(printf, std::move(myp) );  // move 的作用是将myp这个指针转到子线程中去; 这样pan这个指针指向的内存是myp的内存
​
}

用成员函数指针作为线程函数

ClassA myobj(10);
std::thread childthread(&A::thread_work,myobj,15); //  第一个参数是需要放到子线程的成员函数,第二个是该成员该数的实例化对象,第三个是该成员函数需要的参数
​
// 下面两种写法myobj不会调用拷贝构造函数重新拷贝一份
std::thread childthread(&A::thread_work,&myobj,15); //  第一个参数是需要放到子线程的成员函数,第二个是该成员该数的实例化对象,第三个是该成员函数需要的参数
std::thread childthread(&A::thread_work,std::ref(myobj),15); //  第一个参数是需要放到子线程的成员函数,第二个是该成员该数的实例化对象,第三个是该成员函数需要的参数
​


/**
    线程入口函数
*/
void mythread() {
    cout << "run the child thread" << endl;
    MySingle *p_a = MySingle::GetInstance();
    cout << "end" << endl;
    return;
}
​
​
int main(int argc, char *argv[])
{
    /**
    // 第二个参数必须是引用,才能保证是线程安全,这样不会调用拷贝构造
    TestClass testClass;
    std::thread childThread(&TestClass::coutMsgRec, &testClass);  
    std::thread childThread1(&TestClass::isMsgRec, &testClass);
​
    childThread.join();
    childThread1.join();
    */
    
    // 成员方法创建子线程
    
    std::thread myobj1(mythread); 
    std::thread myobj2(mythread);
    myobj1.join();
    myobj2.join();
    
    return 0;
}
#include <thread>
#include <vector>
#include <string>
#include <iostream>
using namespace std;
​
/**
    线程入口函数
*/
void threadentry(int i) {
    cout << i << "  id : "<< std::this_thread::get_id() << " \n"<< endl;
    // todo
}
​
int main(int argc, char *argv[])
{
    // 传递临时对象作为线程参数;
    vector<thread> childthread;
    // 多个线程执行顺序是乱的,跟操作系统调度顺序有关;
    for (int i = 0; i < 10; ++i) {
        childthread.push_back(std::thread(threadentry, i)); // 创建并开始执行
    }
    // todo
    for (auto iter = childthread.begin(); iter != childthread.end(); ++iter) {
        iter->join();  // 等待是个线程都执行完,主线程才能结束
    }
    // TODO 
    return 0;
}
 

2、 数据共享问题 即读写锁的问题

互斥量(mutex)、锁、死锁 等情况

互斥量是一个类对象,相当于一把锁;其成员方法lock和unlock可以加锁和解锁

#include <thread>
#include <vector>
#include <string>
#include <iostream>
#include <mutex>
​
using namespace std;
​
class TestClass {
public:
    void isMsgRec() {
        for (int i = 0; i < 100;++i) {
            m_mutex.lock(); // 这个锁只能解决写时的问题,即多个线程同时写时候,加锁解决冲突,但是读和写还是会有并发冲突
            vec.push_back(i);
            m_mutex.unlock();
            cout << i << "  " << std::this_thread::get_id() << "\n";
        }
    }
​
    bool outMsgRecFlag(int &commd) {
        //  操作容器的过程都要加锁,保证线程安全
        m_mutex.lock();   // lock_gard 是一个类模板
        
        
        // std::lock_guard<std::mutex> sbguard(m_mutex);  // 加这行后所有的lock和unlock 都可以取消,在lock_guard 的构造函数里面执行了lock,
                                                       // 析构函数里面执行了unlock函数;灵活度不如原生的lock
        if (!vec.empty()) {
            int commd = vec.front();
            vec.pop_back();
            m_mutex.unlock();  // 每个分支都要解锁,几个出口就要几个解锁
            return true;
        }
        m_mutex.unlock();
        return false;
    }
​
    void coutMsgRec() {
        for (int i = 0; i < 100; ++i) {
            bool flag = outMsgRecFlag(i);
            cout << i << "  " << std::this_thread::get_id() << "\n";  
        }
    }
private:
    std::vector<int> vec;
    std::mutex m_mutex;
};
​
int main(int argc, char *argv[])
{
    TestClass testClass;
    std::thread childThread(&TestClass::coutMsgRec, &testClass);  // 第二个参数必须是引用,才能保证是线程安全,这样不会调用拷贝构造
    std::thread childThread1(&TestClass::isMsgRec, &testClass);
​
    childThread.join();
    childThread1.join();
    // TODO 
    return 0;
}

3、死锁问题

void func1(){
	mutex1.lock();
    mutex2.lock();
    ​     //todo
    mutex2.unlock();
    mutex1.unlock();
}
void fun2(){
	mutex2.lock();
    mutex1.lock();
    ​     //todo
        std::lock(mutex1,mutex2);  // 这种方式可以避免死锁, 此时需要两个分别解锁   该方法是一次锁定多个互斥量
        //  下面的不需要自己手动解锁
        std::lock_guard<std:mutex>  sbgard(mutex1,std::adopt_lock); // std::adopt_lock 是一个结构体对象,其一个标志作用表示互斥量已经lock过了不需要再次lock
        std::lock_guard<std:mutex>  sbgard(mutex2,std::adopt_lock);

    mutex1.unlock();
    mutex2.unlock();
}
int main(int argc,char *args[]){
    std::thread childthread1(func1);
    std::thread childthread2(func1);
    return 0;
}

解决方案: 互斥量加锁的顺序保持一致

4、unique_lock 的使用

unique_lock 是一个类模板,实际工作中推荐使用 lock_guard,其构造函数里面调用lock,析构函数里面调用unlock,使用起来不太灵活,

此时unique_lock 灵活了很多,但是效率上和内存占用上就更差一点了。

1) adopt_lock

std::unique_lock<std::mutex> qlock(m_mutex1,std::adopt_lock); //adopt_lock同样表示mutex1 这个互斥量已经在unique_lock的构造函数中lock了,不需要再次lock
std::chrono::milliseconds dura(2000);
std::this_thread::sleep_for(dura); // 线程休眠

加锁的语句,如果拿不到锁,就会一直卡在这个位置

2) try_to_lock

std::try_to_lock ,尝试用mutex的lock去锁定,如果没成功,也会立即返回,并不阻塞程序。

class TestClass {
public:
    void isMsgRec() {
        for (int i = 0; i < 1000;++i) {
            
        //  m_mutex1.lock();
        //  std::unique_lock<std::mutex> adgard(m_mutex1,std::adopt_lock);  // 上面的加过了锁,adopt_lock 导致这行代码不会再加锁了
​
            std::unique_lock<std::mutex> adlock(m_mutex1, std::try_to_lock); // 尝试去加锁,此时如果前面已经加锁了,这个锁还是会加,
                                                                             // 因为没有adopt_lock 来说明这个锁是不可重入的
                                                                             // 所以用try_to_lock 的前提是前面不能手动加锁
            if (adlock.owns_lock()) {
                vec.push_back(i);
                cout << i << "  ======" << std::this_thread::get_id() << "\n";
            }
            else {
                // 没有拿到锁
                cout << i << "   no lock   \n";
            }
            
        }
    }
​
    bool outMsgRecFlag(int &commd) {
​
        std::unique_lock<std::mutex> qlock(m_mutex1);
        std::chrono::milliseconds dura(2000);
        std::this_thread::sleep_for(dura); // 线程休眠
​
        if (!vec.empty()) {
            int commd = vec.front();
            vec.pop_back();
            return true;
        }
        return false;
    }
​
    void coutMsgRec() {
        for (int i = 0; i < 1000; ++i) {
            bool flag = outMsgRecFlag(i);
            cout << i << "  ......" << std::this_thread::get_id() << "\n";
        }
    }
private:
    std::vector<int> vec;
    std::mutex m_mutex;
    std::mutex m_mutex1;
};
​
​
​
​
int main(int argc, char *argv[])
{
    
    TestClass testClass;
    std::thread childThread(&TestClass::coutMsgRec, &testClass);  // 第二个参数必须是引用,才能保证是线程安全,这样不好调用拷贝构造
    std::thread childThread1(&TestClass::isMsgRec, &testClass);
​
    childThread.join();
    childThread1.join();
    return 0;
}

3) defer_lock

使用这个参数的前提与try_to_lock 一样,不能手动去先 lock,否则会报异常

表示初始化了一个没有加锁的mutex

a) lock() 和unlock()的使用

std::unique_lock<std::mutex> adlock(m_mutex1, std::defer_lock);
adlock.lock(); // 加锁,此时会自动解锁,也可以手动解锁
​
vec.push_back(i);
cout << i << "  ======" << std::this_thread::get_id() << "\n";
​
adlock.unlock(); // 临时解锁,然后处理非共享代码          
 // todo 
​
adlock.lock(); // 处理完非共享数据后再次lock,处理共享代码            
// todo
​

b) try_lock() 的使用

std::unique_lock<std::mutex> adlock(m_mutex1, std::defer_lock);
            // try_lock 尝试加锁,加上返回true,没加上返回false
            
if (adlock.try_lock()) {
    vec.push_back(i);
    cout << i << "  ======" << std::this_thread::get_id() << "\n";
}
else {
    // todo
}

c) release 的使用

返回它所管理的mutex对象指针,并释放所有权,也就是所unique_lock 和mutex 不在关联

如果原来mutex处于加锁状态,需要接管过来并负责解锁

std::unique_lock<std::mutex> adlock(m_mutex1, std::defer_lock);
// try_lock 尝试加锁,加上返回true,没加上返回false
std::mutex *ptx = adlock.release(); // 上一行已经加锁了,此时接管后需要手动解锁
vec.push_back(i);
cout << i << "  ======" << std::this_thread::get_id() << "\n";
ptx->unlock();  // 接管后手动解锁
​
// 此时ptx 的地址和m_mutex1 的地址是相同的

这个成员函数是为了控制锁的粒度,选择合适的粒度很重要

4) unique_lock 所有权的传递

所有权:一个mutex只能和一个unique_lock 绑定,不能绑定多个

a) 通过std::move 来转移所有权

std::unique_lock<std::mutex> adlock(m_mutex1);
std::unique_lock<std::mutex> adlock1(std::move(m_mutex1)); // move 是调用移动构造函数,另行绑定
std::mutex *ptx = adlock.release(); // 此时拿到的是空的
std::mutex *ptx = adlock1.release(); 

b) 通过return返回

std::unique_lock<std::mutex> rtn_unique_lock() {
        std::unique_lock<std::mutex> tmpgard(m_mutex);
        return tmpgard; // 从函数返回一个局部的对象是可以的,会通过移动构造函数返回一个临时的对象,并调用unique_lock 的移动构造函数
}

5 、其他保护共享数据的机制

1)单例设计模式

例如配置文件的读写等,整个项目只需要一个,始终只有一个对象

class MySingle {
public:
    static MySingle *GetInstance() {
        if (m_instance == nullptr) {
            m_instance = new MySingle;
            static CCarhuishou c1; // 程序退出的时候会调用这个类的析构函数,析构这个对象,静态变量的生命周期一直到程序退出
        }
        return m_instance;
    }
    // 类中类
    class CCarhuishou {
    public:
        ~CCarhuishou() {
            if (MySingle::m_instance != nullptr) {
                delete MySingle::m_instance;
                MySingle::m_instance = nullptr;
            }
        }
    };
private:
    MySingle() {
        ;
    };
    static MySingle *m_instance;
};
​
​
int main(int argc, char *argv[])
{
    MySingle *m_a = MySingle::GetInstance(); // 静态类直接通过类名可以调用,不需要通过类对象
    MySingle *m_b = MySingle::GetInstance(); // m_a = m_b
​
    return 0;
}

通常需要在主线程中,在调用其他线程之前,创建单例模式中的类,将该载入的数据载入其中,后续只读,这样子线程在操作这个单例的时候就是数据安全的

2) 单例设计模式解决共享数据

当需要在子线程中,而不是主线程中创建单例对象,就需要考虑线程安全问题

class MySingle {
public:
    static MySingle *GetInstance() {
        // 双重检查的单例模式
        if (m_instance == nullptr) { // 如果不加这个非空判断,只有在第一次创建的时候锁才有作用,后续其他调用都是只读,影响效率
            std::unique_lock<std::mutex> mylock(m_mutex); // 自动加锁,
            if (m_instance == nullptr) {
                m_instance = new MySingle; // 多个线程同时要访问的写内容
                static CCarhuishou c1; // 程序退出的时候会调用这个类的析构函数,析构这个对象,静态变量的生命周期一直到程序退出
            }
        }
        return m_instance;
    }
    // 类中类
    class CCarhuishou {
    public:
        ~CCarhuishou() {
            if (MySingle::m_instance != nullptr) {
                delete MySingle::m_instance;
                MySingle::m_instance = nullptr;
            }
        }
    };
private:
    MySingle() {
        ;
    };
    static MySingle *m_instance;
};
​
​
/**
    线程入口函数
*/
void mythread() {
    cout << "run the child thread" << endl;
    MySingle *p_a = MySingle::GetInstance();
    cout << "end" << endl;
    return;
}
​
​
int main(int argc, char *argv[])
{
    /**
    // 第二个参数必须是引用,才能保证是线程安全,这样不会调用拷贝构造
    TestClass testClass;
    std::thread childThread(&TestClass::coutMsgRec, &testClass);  
    std::thread childThread1(&TestClass::isMsgRec, &testClass);
​
    childThread.join();
    childThread1.join();
    */
​
    // 成员方法创建子线程
    std::thread myobj1(mythread); 
    std::thread myobj2(mythread);
    myobj1.join();
    myobj2.join();
​
    return 0;
}

3) std::call_once() 的用法

c++ 11 引入的函数,其第二个参数是一个函数名,其功能是保证第二个参数表示的函数只能调用一次

具备互斥量的能力,而且效率上比互斥量消耗的资源更少。

需要与一个标记std::once_flag 结合使用,这个标记是一个结构;通过这个标记决定这个函数是否执行,第一次调用后设置为已调用状态

std::once_flag g_flag;  //       
​
class MySingle {
public:
    static void createInstance() { // 只被调用一次
        m_instance = new MySingle;
        static CCarhuishou c1;
    }
​
    static MySingle *GetInstance() {
        std::call_once(g_flag, createInstance);
        return m_instance;
    }
    // 类中类
    class CCarhuishou {
    public:
        ~CCarhuishou() {
            if (MySingle::m_instance != nullptr) {
                delete MySingle::m_instance;
                MySingle::m_instance = nullptr;
            }
        }
    };
​
private:
    MySingle() {
        ;
    };
    static MySingle *m_instance;
};
​

6、条件变量 condition_variable、wait、notify_one、notify_all

std::async 、std::future 创建后台任务并返回值;

std::async 是一个函数模板,用来启动一个异步任务,启动起来一个异步任务之后,返回一个std::future 对象,也是一个类模板

启动一个异步任务 就是自动创建一个线程并开始执行对应的入口函数,返回一个std::future对象,这个对象里面含有线程的入口函数所返回的结果

也就是线程返回值,通过get函数或者这个返回值

future 提供一种访问异步操作结果的机制;

1)、创建带返回值的异步线程

int mythread() { 
    cout << "start thread; ID" << std::this_thread::get_id() << endl;
    std::chrono::milliseconds dura(9000);
    std::this_thread::sleep_for(dura);
​
    cout << "start end; ID" << std::this_thread::get_id() << endl;
    return 5;
}
int main(int argc, char *argv[])
{
    cout << "start main thread; ID" << std::this_thread::get_id() << endl;
    std::future<int> result = std::async(mythread);
    int def = 0;
    // cout << "the result :" << result.get() << endl; // get() 函数是阻塞的,等待线程执行结束并返回值;才会结束  get只能调用一次
    
    result.wait(); // 等待线程结束,本身并不返回结果,类似于join,阻塞在这里等子线程结束在继续下面的代码
    cout << "test dd";
    return 0;
}

类的成员函数做子线程

class ChildClass{
public:
    int mythread(int  mypr) { 
        cout << mypr;
        cout << "start thread; ID" << std::this_thread::get_id() << endl;
        std::chrono::milliseconds dura(9000);
        std::this_thread::sleep_for(dura);
​
        cout << "start end; ID" << std::this_thread::get_id() << endl;
        return 5;
    }
};
int main(int argc, char *argv[])
{
    ChildClass childclass;
    int tmpprt = 12;
    cout << "start main thread; ID" << std::this_thread::get_id() << endl;
    std::future<int> result = std::async(&ChildClass::mythread,&childclass,tmpprt); // 第二个参数是一个对象的引用
    int def = 0;
    // 如果get 和wait都不调用,主线程结束了子线程还在执行
    // cout << "the result :" << result.get() << endl; // get() 函数是阻塞的,等待线程执行结束并返回值;才会结束  get只能调用一次
    result.wait(); // 等待线程结束,本身并不返回结果,类似于join,阻塞在这里等子线程结束在继续下面的代码
    cout << "test dd";
    return 0;
}

std::launch 枚举类的用法

int main(int argc, char *argv[])
{
    // 通过额外向std::async 传递一个参数,该参数是std::launch类型(枚举) 表达一些特殊含义
    // std::launch::deferred 表示线程入口函数调用被延迟到std::future的wait 或者get函数调用的时候才执行
    // 其实就是延迟调用,并且不会创建子线程,mythread是在主线程中执行的
​
​
    ChildClass childclass;
    int tmpprt = 12;
    cout << "start main thread; ID" << std::this_thread::get_id() << endl;
    
    
    // std::launch::async 会创建一个子线程并立马执行,(系统默认就是这个参数,跟不写launch一样)
    
    
    std::future<int> result = std::async(std::launch::deferred, &ChildClass::mythread, &childclass, tmpprt); // 第二个参数是一个对象的引用
    int def = 0;
    
    cout << "the result :" << result.get() << endl; // get() 函数是阻塞的,等待线程执行结束并返回值;才会结束  get只能调用一次
    //result.wait(); // 等待线程结束,本身并不返回结果,类似于join,阻塞在这里等子线程结束在继续下面的代码
    cout << "test dd";
    return 0;
}

2 、std::packaged_task 的用法

打包任务,把任务包装起来,也是一个类模板,模板参数是各种可调用参数,通过 std::packaged_task 把各种可调用对象包装起来,方便线程调用

包装一个成员方法

int mythread(int  mypr) { 
        cout << mypr;
        cout << "start thread; ID" << std::this_thread::get_id() << endl;
        std::chrono::milliseconds dura(9000);
        std::this_thread::sleep_for(dura);
​
        cout << "start end; ID" << std::this_thread::get_id() << endl;
        return 5;
}
​
int main(int argc, char *argv[])
{
    int tmpprt = 12;
    cout << "start main thread; ID" << std::this_thread::get_id() << endl;
    std::packaged_task<int(int)> mypt(mythread); // int(int)  第一个int 是返回的结果类型,第二个是线程入口函数的参数类型
    std::thread t1(std::ref(mypt), tmpprt); // 线程直接开始执行
    cout << "test dd";
    t1.join();
    cout << "test aaaa";
    std::future<int> result = mypt.get_future(); // 里面存的是返回的结果,需要通过get获取
​
    cout << "result" << result.get();
​
    return 0;
}
 

包装一个lambda表达式

int main(int argc, char *argv[])
{
    int tmpprt = 12;
    cout << "start main thread; ID" << std::this_thread::get_id() << endl;
    // std::packaged_task<int(int)> mypt(mythread); // int(int)  第一个int 是返回的结果类型,第二个是线程入口函数的参数类型
    
    std::packaged_task<int(int)> mypt([](int mypar) {
        cout << mypar;
        cout << "\n start thread; ID" << std::this_thread::get_id() << endl;
        std::chrono::milliseconds dura(9000);
        std::this_thread::sleep_for(dura);
​
        cout << "\n start end; ID" << std::this_thread::get_id() << endl;
        return 5;
    });
​
    std::thread t1(std::ref(mypt), tmpprt); // 线程直接开始执行
    cout << "test dd \n";
    t1.join();
    cout << "test aaaa \n";
    std::future<int> result = mypt.get_future(); // 里面存的是返回的结果,需要通过get获取
​
    cout << " \n result" << result.get();
    return 0;
}

将打包的内容放到容器里面

std::vector <std::packaged_task<int(int)>> mytack;
​
int main(int argc, char *argv[])
{
    int tmpprt = 12;
    cout << "start main thread; ID" << std::this_thread::get_id() << endl;
    // std::packaged_task<int(int)> mypt(mythread); // int(int)  第一个int 是返回的结果类型,第二个是线程入口函数的参数类型
    
    std::packaged_task<int(int)> mypt([](int mypar) {
        cout << mypar;
        cout << "\n start thread; ID" << std::this_thread::get_id() << endl;
        std::chrono::milliseconds dura(9000);
        std::this_thread::sleep_for(dura);
​
        cout << "\n start end; ID" << std::this_thread::get_id() << endl;
        return 5;
    });
    mytack.push_back(std::move(mypt));
​
    std::packaged_task<int(int)> mypt2;
    auto iter = mytack.begin();
    mypt2 = std::move(*iter);
    mytack.erase(iter);  // 移除这一项,删除,删除后这个迭代器失效
    cout << "babba";
    mypt2(100); // 直接调用而不是构建子线程的话是单线程串行
    return 0;
}

3) 、std::promise 类模板

能够在某个线程中给它赋值,然后在其他线程中将这个值取出来

实现两个线程中的数据传递

void mythread(std::promise<int> &tmp,int calc) { 
​
    calc++;
    calc += 10;
    cout << "start thread; ID" << std::this_thread::get_id() << endl;
    std::chrono::milliseconds dura(9000);
    std::this_thread::sleep_for(dura);
​
    int result = calc; // 计算结果
    tmp.set_value(result); // 在子线程中赋值
    cout << "start end; ID" << std::this_thread::get_id() << endl;
}
​
void mythread2(std::future<int> &tmp) {
    auto result = tmp.get();
    cout << "thread2" << result;
}
​
int main(int argc, char *argv[])
{
    std::promise<int> mypto; // 声明一个promise对象,保存的类型是int
    std::thread t1(mythread, std::ref(mypto), 10);
​
    t1.join();
​
    // 获取结果值
    std::future<int> fur = mypto.get_future(); // promise 和future 绑定,用于获取线程返回值
​
    std::thread t2(mythread2,std::ref( fur));
    //int res = fur.get();
    t2.join();
    // cout << "result " << res;
    return 0;
}

4)、future_status 的用法

future_status 有三个状态,timiout 超时、ready 运行、deffered 延时,wait_for 为子线程返回一个状态

int mythread() { 
​
    cout << "start thread; ID" << std::this_thread::get_id() << endl;
    std::chrono::milliseconds dura(5000);
    std::this_thread::sleep_for(dura);
​
    cout << "end thread ; ID" << std::this_thread::get_id() << endl;
    return 5;
}
​
int main(int argc, char *argv[])
{
    std::future<int> res = std::async(mythread);
​
    cout << "wait for \n";
    std::future_status status = res.wait_for(std::chrono::seconds(8)); // 等待
    if (status == std::future_status::timeout) {
        // 线程还没执行完,如果等待时间到,子线程还没执行完,子线程不会继续执行了
        cout << "the thread is running \n";
    }
    else if (status == std::future_status::ready) {
        cout << "res  : " << res.get();
    }
    else if (status == std::future_status::deferred) { // 延迟  需要 async 的第一个参数设置为std::launch::deffer才成立
        res.get(); // get 调用的时候才会执行子线程的内容
    }
​
    cout << "end" ;
    return 0;
}

5)std::shared_future 的用法

第二次从future对象中调用get,程序会报异常,主要是因为get函数的设计,是一个移动 语义,第一次调用之后被移走之后就是空的了;

而shared_future 的get函数不再是转移,而是复制数据;

int mythread(int cnt) { 
​
    cout << "start thread; ID" << std::this_thread::get_id() << endl;
    std::chrono::milliseconds dura(5000);
    std::this_thread::sleep_for(dura);
    cnt++;
    cout << "end thread ; ID" << std::this_thread::get_id() << endl;
    return 5;
}
​
std::vector <std::packaged_task<int(int)>> mytack;
​
int main(int argc, char *argv[])
{
    cout << "main thread" << std::this_thread::get_id() << endl;
    std::packaged_task<int(int)> mypt(mythread);
    std::thread t1(std::ref(mypt),1);
    t1.join();
​
    std::future<int> result = mypt.get_future();
​
    // 执行完毕后result里面的内容会移动到result_s,result_s 里面的值能多次get
    std::shared_future<int> result_s(std::move(result)); // std::move 将左值转换成右值
    // std::shared_future<int> result_s(result.share());  // 两种用法二选一
​
    bool flag = result.valid(); // 能判断里面是否有值
    if (flag) {
        result.get();
    }
}

6) 、std::atomic 原子操作

std::atomic 是一个模板

从汇编的角度看 ,++ 操作可能包含几个步骤,因此在多线程环境下这个操作并不是线程安全的,可以引入互斥量加锁来保证线程安全;

但是原子操作,可以认为是一种无锁的并发控制,表示这个步骤不能被打断,然后保证线程安全

原子操作就是不会被打断的代码,比加锁效率高

int g_cnt = 0;
​
/**
    线程入口函数
*/
void  mythread() { 
​
    for (int i = 0; i < 100000; ++i) {
        g_cnt++;
    }
}
​
int main(int argc, char *argv[])
{
    thread t1(mythread);
    thread t2(mythread);
​
​
    t1.join();
    t2.join();
    cout << g_cnt;  // 由于++ 不是原子操作,最终的结果不是 200000
    return 0;
}
互斥量的加锁一般是针对一个代码段,而原子操作针对的是一个变量而不是代码段。

std::atomic<int>  g_cnt = 0;
​
std::atomic<bool> m_flag = false;
​

原子操作只能处理变量的读写,赋值等简单操作,常用于计数、统计等工作;

原子操作一般针对于 ++ -- += -= &= |= 是支持的;

如果系统资源紧张 std ::thread 创建线程可能会失败;

std::async 创建一个异步任务;

std::launch::async 这个作为async的第一个参数,表示这个异步任务必须要创建一个新线程来执行任务

如果async不给第一个参数,系统默认是 std::launch::deferred | std::launch::async ;二者选其一,系统自动觉得使用哪种;

std::async 和std::thread 的区别

thread一定会创建线程,如果创建失败,程序会崩溃;

async不一定会创建新线程可能会失败;这是一个异步任务,且容易拿到返回值

系统资源紧张的时候async就不会创建新线程了,而是后续调用get的时候同步执行

std::future_status status = res.wait_for(std::chrono::seconds(0)); // 等待
if (status == std::future_status::deferred) {
    // 异步任务被延迟调用,系统资源紧张了,采用了get调用时候同步执行的方式
    res.get();
}
else if (status == std::future_status::ready) {
    // 异步任务创建了新线程来执行
}
 

c++11 的互斥量是不允许连续调用两次的,但是在windows临界区是可以的

1)、递归的独占互斥量

7) 、wait / notify / notify_all notify_one的用法

都要与条件变量std::condition_variable 配合使用

std::condition_variable my_cond;

my_cond.wait(互斥量 , true| false); // 第二个参数是true,不堵塞,false,wait将解锁互斥量,并堵塞到本行

线程A: 等待一个条件满足

线程B:往消息队列里面扔消息;

线程B满足线程A 的条件后,触发线程A执行

class A {
public:
    /**
      向容器中写元素
    */
    void inMsgRecQueue() {
        for (int i = 0; i < 10000;++i) {
            cout << "add an element" << endl;
            std::unique_lock<std::mutex> aggard(m_mutex);
            msgRecQue.push_back(i);
        }
    }
​
    /**
       从容器中取元素
    */
    void outMSgRecQueue() {
        int commd = 0;
        for (int i = 0; i < 10000;++i) {
            bool result = outMsg(commd);
            if (result) {
                cout << "get an element from queue \n";
            }
            else {
                cout << "no element \n";
            }
        }
        cout << "end \n";
    }
​
    /**
        判断容器是否为空
    */
    bool outMsg(int &commd) {
        // 采用双重检查的方式判断降低锁的粒度,提高效率
        if (!msgRecQue.empty()) {
            std::unique_lock<std::mutex> sbgard1(m_mutex);
            if (!msgRecQue.empty()) { // 操作共享数据需要加锁
                commd = msgRecQue.back();
                msgRecQue.pop_back();
                return true;
            }
        }
        return false;
    }
private:
    std::mutex m_mutex;
    std::vector<int> msgRecQue;
};
​
int main(int argc, char *argv[])
{
    A myobj;
    std::thread myinThread(&A::inMsgRecQueue, &myobj);
​
    std::thread myoutThread(&A::outMSgRecQueue,&myobj);
​
    myinThread.join();
    myoutThread.join();
    return 0;
}

std::condition_variable 实际上是一个类,是一个和条件相关的类,必须和互斥量结合使用

class A {
public:
    /**
      向容器中写元素
    */
    void inMsgRecQueue() {
        for (int i = 0; i < 10000;++i) {
            cout << "add an element \n" << endl;
            std::unique_lock<std::mutex> aggard(m_mutex);
            msgRecQue.push_back(i);
            // 有元素后可以通知另一个线程取元素
            my_condition.notify_one(); // 唤醒wait 的线程,当有多个线程wait的时候,只能唤醒其中一个
            // my_condition.notify_all(); // 能唤醒所有在随眠的线程
        }
    }
​
    /**
       从容器中取元素
    */
    void outMSgRecQueue() {
        int commd = 0;
        while (true) {
            std::unique_lock<std::mutex> sbgard1(m_mutex);
            // wait 是一个成员函数,第一个参数是一个互斥量,
            // 第二个参数如果返回false,wait将解锁互斥量并堵塞到本行
            // 堵塞到其他线程调用notify_one 为止
            // 如果第二个参数是true,wait直接返回,相当于不等待
​
            // 当其他线程将这个wait唤醒后,wait 将不断的尝试重新获取互斥量锁,如果获取不到,流程还是会阻塞在wait处
            // 获取到之后并上锁,然后执行:
                 // 1) wait 有第二个参数,判断第二个参数的状态,当第二个参数任然是false,继续阻塞,第二个是true,则正常执行
                // wait 没第二个参数,则认为是true
            my_condition.wait(sbgard1, [this]() {
                if (!msgRecQue.empty()) {
                    cout << "true \n";
                    return true;
                }
                cout << "false \n";
                return false;
            });
            cout << "get element \n";
            commd = msgRecQue.back();
            msgRecQue.pop_back();
            cout << "commd" << commd << endl;
            sbgard1.unlock();
            // 执行不用锁住的程序
        }
    }
    /**
        判断容器是否为空
    */
    bool outMsg(int &commd) {
        // 采用双重检查的方式判断降低锁的粒度,提高效率
        if (!msgRecQue.empty()) {
            std::unique_lock<std::mutex> sbgard1(m_mutex);
            if (!msgRecQue.empty()) { // 操作共享数据需要加锁
                commd = msgRecQue.back();
                msgRecQue.pop_back();
                return true;
            }
        }
        return false;
    }
private:
    std::mutex m_mutex;
    std::vector<int> msgRecQue;
    std::condition_variable my_condition; // 生成一个条件变量对象
};
​
int main(int argc, char *argv[])
{
    A myobj;
    std::thread myinThread(&A::inMsgRecQueue, &myobj);
​
    std::thread myoutThread(&A::outMSgRecQueue,&myobj);
    std::thread myoutThread(&A::outMSgRecQueue, &myobj);
​
    myinThread.join();
    myoutThread.join();
    return 0;
}

原子操作没有拷贝构造函数;

atomic<int> st1 = 0;
//  atomic<int> st2 =st1 // 非法操作,系统不允许,没有拷贝构造函数
    
atomic<int> st2(st1.load());    

标准库中的内容(std),在进入构造函数之前都会调用默认构造函数进行树池化,因此赋值操作是假的初始化;内置类型不一定(如 int );

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zero _s

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值