zoukankan      html  css  js  c++  java
  • C++11多线程笔记

    #include <thread>
    #include <iostream>
    #include <string>
    using namespace std;
    
    /*
    如果主线程执行完毕,就代表整个进程执行完毕了,此时
    一般情况下,如果其他子线程还没有执行完毕,那么这些子线程也会被
    操作系统强行终止;
    所以如何想保持子线程运行状态的话,那么大家要让主线程保持执行;
    例外情况下,以下介绍;
    */
    
    thread mytoj(myprint);
    //join阻塞主线程,并等待主线程执行完毕;
    myobj.join();
    
    /*
    传统多线程主程序要等待子程序执行完毕,然后自己再最后退出;
    detach:分离,也就是主线程不和子线程会和了,主子线程分开执行;
    一旦detach(),这个子线程就与主线程失去关联;
    这个子线程就相当于被C++运行时库接管,执行完毕后,
    由运行时库负责清理相关的资源。(守护进程)、
    一旦调用了detach(),就不能再用join(),否则则系统会报错;
    
    joinable():判断是否可以成功使用join()或者detach()的;
    */
    
    thread myobj(myprint);
    if (myobj.joinable())
    {
    
    }
    else
    {
    
    }
    
    /*
    其他创建线程的方法:
    1,使用lambda表达式
    */
    
    auto mylamthread = [] {
        cout << "my thread begin1" << endl;
        cout << "mythread end1" << endl;
    }
    
    thread myobj4(mylamthread);
    myobj4.join();//or myobj4.detach();
    
    //类的形式调用多线程
    class TA
    {
    public:
        void operator()()//不能带参数
        {
            cout << "my thread begin" << endl;
            cout << "my thread end" << endl;
        }
    };
    
    //主函数中调用;
    TA ta;
    thread myobj3(ta);
    myobj3.join();//等待子线程执行结束
    
    //错误示范:注意:如果主线程中先结束的话,子线程
    //回到回台执行的话,打印语句可能无法打印部分或者全部语句;
    
    //大坑提示:
    /*
        m_i使用的是myi,当主线程结束后,myi被删除,
        而m_i是一个引用,所以会导致打印不可预料;
    */
    class TA
    {
    public:
        int& m_i;
        TA(int& i) :m_i(i) {}
        void operator()()//不能带参数
        {
            cout << "my thread begin" << m_i << endl;
            cout << "my thread end" << m_i << endl;
        }
    };
    
    //主函数中调用;
    int myi = 6;
    TA ta(myi);
    thread myobj3(ta);
    myobj3.detach();
    
    /*
    问题:以上主线程结束,ta创建的对象也被释放,这时调用
    对象的程序函数不会出错嘛?
    
    答:此时对象ta是被复制到线程中去的,所以执行完线程后ta被销毁,
    但是复制的ta对象依然存在,所以没有问题;
    前提是:这个TA这个类没有引用或者指针类型的对象,
    可以通过修改类的成员函数来查看;
    */
    
    class TA
    {
    public:
        int& m_i;
        TA(int& i) :m_i(i) {}
        TA(const TA& ta) : m_i(ta.m_i)
        {
    
        }
    
        ~TA()
        {
    
        }
        void operator()()//不能带参数
        {
            cout << "my thread begin" << m_i << endl;
            cout << "my thread end" << m_i << endl;
        }
    };
    
    //主函数中调用;
    int myi = 6;
    TA ta(myi);
    thread myobj3(ta);
    myobj3.detach();
    
    //正确写法:不能使用引用:如下:
    
    class TA
    {
    public:
        int m_i;
        TA(int i) :m_i(i) {}
        TA(const TA& ta) : m_i(ta.m_i)
        {
    
        }
    
        ~TA()
        {
    
        }
        void operator()()//不能带参数
        {
            cout << "my thread begin" << m_i << endl;
            cout << "my thread end" << m_i << endl;
        }
    };
    
    //主函数中调用;
    int myi = 6;
    TA ta(myi);
    thread myobj3(ta);
    myobj3.detach();
    
    //第二节课
    using namespace std;
    
    /*
    分析认为,i此时是拷贝的内容,是值不是引用;是值传递,因此使用detach()时
    没有问题;但是不推荐使用这个方式;
    detach()使用指针,绝对有问题,比如以下pmybuf;
    那如何传递字符串呢?
    */
    void myprint(const int& i, char* pmybuf)
    {    cout << i << endl;
        cout << pmybuf << endl;
        return;
    }
    
    int main()
    {
        //一,传递临时对象作为线程参数
        //要避免的陷阱
        int mvar = 1;
        int& mvary = mvar;
        char mybuf[] = "this is a test!";
        thread myobj(myprint,, mvar(, mybuf);
        myobj.join();
        //myobj.detach();//
    }
    
    /*
    修改为如下情况下是否可以呢?
    事实上,存在可能性:mybuf都被回收了,即
    main()函数执行完了,系统才把mybuf转化为string对象;
    const string &pmybuf,其实不是使用的引用,而是调用了拷贝构造函数;
    重新创建了一个对象;
    */
    void myprint(const int& i, const string &pmybuf)
    {
        cout << i << endl;
        cout << pmybuf.c_str() << endl;
        return;
    }
    
    int main()
    {
        int mvar = 1;
        int& mvary = mvar;
        char mybuf[] = "this is a test!";
        //使用隐式转换是不安全的;
        thread myobj(myprint, , mvar, mybuf);//错误
        //在创建线程的同时构造临时对象的方法传递参数是可行的;
        thread myobj(myprint, , mvar, string(mybuf));//修改为正确的方法
        //myobj.join();
        myobj.detach();//
        cout << "i love china";
    }
    
    //总结:
    /*
     若传递int这种简单类型参数,建议都是值传递,不要使用引用传递;
     如果传递类对象,避免隐式类型转化;全部都在创建线程这一行就构造出来;
     最好使用join(),而不要使用detach()函数;
     心得:如果对一些问题不清楚的话,可以使用测试的方法来检测一些
     东西是否如推测的那样;
    */
    
    /*
    每个线程不官是主线程,都有一个id,库函数中id的方法:
    std::this_thread::get_id();
    */
    //此时传递的是对象的拷贝,不是真正的引用类型;
    void myprint(A& pmybuf)
    {
        pmybuf.m_i = 100;
        cout << "fee" << "thread_id" << std::this_thread::get_id() << endl;
    }
    
    int main()
    {
        A myobj(10);//生成一个对象
        //std::ref()此时是真正的对象的引用类型;不会再被拷贝出来一份;
        std::thread mytobj(myprint2, std::ref(myobj));
        mytobj.join();
        return 0;
    }
    
    //move使用
    void myprint2(unique_ptr<int> pzn) {
    
    }
    
    int main()
    {
        unique_ptr<int> myp(new int(100));
        std::thread mytobj(myprint2, std::move(myp));
        mytobj.join();
        return 0;
    }
    
    //用成员函数指针做线程函数:的例子:
    //thread_work是类A的成员函数;
    int main()
    {
        A myobj(10);
        std::thread myobj(&A::thread_work, myobj, 15);//15是线程函数的参数;
        myobj.join();
        return 0;
    }
    
    或者:
    
    class A
    {
        void operator()(int num)
        {
    
        }
    };
    
    int main()
    {
        A myobj(10);
        //此时调用的函数是上述的operator()(int num)函数;
        //不调用拷构造函数了;如果调用detach()就不安全了;容
        std::thread myobj(std::ref(myobj),15);
        myobj.join();
    }
    
    
    //多个线程处理问题
    void myprint(int num)
    {
        cout << "myprint start run:" << num << endl;
        //....
        cout << "myprint end" << num << endl;
    }
    
    int main()
    {
        //创建和等待多个线程
        vector<thread> mythreads;
        //a,多个线程执行顺序是乱的,和操作系统的运行调度机制有关系;
        //主线程等待所以子线程运行结束,最后主线程运行结束,老师推荐这种join的写法
        //便于对大量线程的管理;
        for (int i = 0; i < 10; i++)
        {
            mythreads.push_back(thread(myprint, i));
        }
    
        for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter)
        {
            iter->join();//等待10个线程都返回
        }
        cout << "i love china;";
    }
    
    //数据共享问题分析
    vector<int> g_v = { 1,2,3 };
    //共享数据;只读数据是安全稳定的,不需要特别的处理,直接读就可以了;
    //有读有写,如何处理呢?
    //最简单的处理:读的时候不能写,写的时候不能读。两个线程不能同时写,8个线程不能同时读;
    //由于任务切换导致各种诡异事情发生,最可能的诡异事情是 程序崩溃;
    
    class A
    {
    public:
        //把收到的消息(玩家命令)入到一个队列的线程
        void inMsgRecvQueue()
        {
            for (int i = 0; i < 10000; ++i)
            {
                cout << "inMsgRecvQueue" << i << endl;
                msgRecvQueue.push_back(i);
            }
        }
    
        void outMsgRecvQueue()
        {
            for (int i = 0; i < 10000; ++i)
            {
                if (!msgRecvQueue.empty())
                {
                    //消息不为空
                    int command = msgRecvQueue.front();
                    msgRecvQueue.pop_front();
                    //这里考虑处理数据;
                }
                else
                {
                    //消息为空
                }
            }
        }
    private:
        std::list<int> msgRecvQueue;
    };
    
    int main()
    {
        A myobja;
        //第二个参数是引用,才能保证线程里用的是同一个对象
        std::thread myOutnMsgObj(&A::outMsgRecvQueue,&myobja);
        std::thread myInMsgObj(&A::inMsgRecvQueue,&myobja);
        myInMsgObj.join();
        myOutnMsgObj.join();
    }
    
    //保护共享数据,操作时某个线程,用代码把共享数据锁住,操作数据,解锁;
    //其他操作数据的线程必须等待解锁,锁定住,操作,解锁;
    
    //“互斥量”
    //互斥量mutex概念;互斥量是类对象,理解成一把锁;
    //多个线程尝试用lock()成员函数加锁这这把锁头,只有一个线程能够锁定成功,
    //只有一个线程能锁定成功,(成功的标志)
    //如果没锁成功,那么流程卡在lock处不断尝试去加锁这把锁;
    /*
    std::mutex m_Mutex;
    m_Mutex.lock();
    m_Mutex.unlock();
    
    //m_Mutex是std::mutex的成员;
    std::lock_guard 类模板;
    ex:
        std::lock_guard<std::mutex> sbguard(m_Mutex);
    
        死锁这个问题,是由至少两个锁头也就是两个互斥量才能产生;
        比如两个线程A,B分别锁住了其中一把锁,但是需要另一个把锁
        但是另一个把锁在另一个线程中,所以产生相互等待对方的锁
        被解锁才能使用;
    */
    
    /*
    死锁的一般解决方案:
    1,互斥量的顺序不搞乱,保持一致;
    std::lock_guard<std::mutex> sbguard1(m_Mutex1);
    std::lock_guard<std::mutex> sbguard2(m_Mutex2);
    
    或者:
    m_Mutex1.lock();
    m_Mutex2.lock();
    m_Mutex1.unlock();
    m_Mutex2.unlock();
    
    
    std::lock()函数模板:
    能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不行,
    1个也不行);
    //它不存在这种因为再多个线程中,因为锁的顺序问题导致死锁的风险;
    
    
    std::lock():如果互斥量中有一个没有锁住,它就等在那里,
    等多有互斥量都锁住,它才往下走;
    如果一个没有锁住,它会释放锁住的互斥量,然后再次去锁住所有的
    互斥量,直到所有的都被锁住。
    如果一个没有锁住,它就释放锁住的互斥量解锁;然后去锁定另一个,
    待会再锁定先前解锁的互斥量;
    同时锁定多个互斥量的场景;
    
    example:
    std::lock(my_mutex1,my_mutex2);
    my_mutex1.unlock();
    my_mutex2.unlock();
    
    
    或者:
    std::lock(my_mutex1,my_mutex2);//相当于每个互斥量都调用了.lock();
    std::lock_guard<std::mutex> sbguard1(my_mutex1,std::adopt_lock);
    std::lock_guard<std::mutex> sbguard2(my_mutex2,std::adopt_lock);
    注释:使用std::adopt_lock表示不会再在对象的构造函数中再次锁定my_mutex1,my_mutex2;
    std::adopt_lock是个结构体对象,起一个标记作用,作用是
    表示结构体对象不需要再lock();
    一次锁定多个互斥量;
    
    
    
    unique_lock取代lock_guard
    unique_lock是个类模板,工作中,一般lock_guard(推荐使用)
    lock_guard取代了mutex的lock()和unlock();
    
     std::unique_lock<std::mutex> sbguard1(my_mutex1);
     std::lock_guard<std::mutex> sbguard1(my_mutex);
     以上两种情况下可以互换;
     std::lock_guard<std::mutex> sbguard1(my_mutex1,std::adopt_lock);//adopt_lock标记作用
     std::adopt_lock:表示这个互斥量已经被lock了(你必须要把互斥量提前lock了,否则会报异常);
     std::adopt_lock标记的效果是“假设调用方线程已经拥有了互斥量的所有权(已经lock()成功了)
     通知lock_guard不需要再构造函数中lock这个互斥量了;
    
     unique_lock也可以带这个标记:std::adopt_lock标记,含义相同;
    
     //休息20s
     std::chrono::milliseconds dura(20000);//20s 
     std::this_thread::sleep_for(dura);
     //
    
    
     std::try_to_lock使用:
     std::try_to_lock尝试取锁,如果成功/失败立即返回,不会阻塞在那里,
     用这个try_to_lock的前提是你自己不能先去lock;
     但是前提之前不能加锁; 
     例如:my_mutex1.lock();此时使用错误;
      std::unique_lock<std::mutex> sbguard1(my_mutex1,std::try_to_lock);
      sbguard1.owns_lock()判定是否拿到锁;
    
      std::defer_lock:
      前提是:你自己不能先lock,否则会报异常;
      defer_lock的意思就是并没有给mutex加锁,初始化一个没有加锁的mutex;
      std::unique_lock<std::mutex> sbguard1(my_mutex1,std::defer_lock);
      sbguard1.lock();//咱们不能自己unlock()
    
    
      unique_lock的成员函数:
      lock();
      unlock();
      try_lock();尝试给互斥量加锁,如果拿不到锁,则返回false;
      如果拿到锁了,返回true;这个函数不阻塞的;
      release(),返回它所管理的mutex对象指针,并释放所有权,也就是说,这个
     unique_lock和mutex不再有关系;严格区分unlock()和release()的区别,不要混淆;
     如果原来mutex对象处于加锁状态,你有责任接管过来并
     负责解锁。(release返回的是原始mutex的指针);
    
      既然咱们不能自己unlock(),那unlock()的作用 又是什么呢?
      1,有时需要暂时解锁处理一些非共享的代码,执行完后可以再次
      lock();
    
    try_lock():使用方法;
    std::unique_lock<std::mutex> sbguard1(my_mutex1,std::defer_lock);
    sbguard1.try_lock() == true;
    sbguard1.try_lock() == false;
    
    relase(),使用方法:
    std::unique_lock<std::mutex> sbguard1(my_mutex1);
    std::mutex* ptx = sbguard1.release();
    ...
    ptx->unlock();自己负责mutex的unlock();
    
    
    
    注意:只有共享数据才需要加锁,不需要的地方千万不需要加锁 ;
    因此要及时加锁,解锁;
    有人也把锁住的代码多少,称为锁的粒度,粒度一般用粗细来描述;
    a/锁住的代码少,这个粒度叫细,执行效率高;
    b/锁住的代码多,粒度叫粗,那执行效率就低;
    要学会尽量选择合适粒度的代码进行保护,粒度太细,可能漏掉共享数据的
    保护,粒度太粗,影响效率;
    选择合适的粒度,是高级程序员的能力和实力的体现;
    
    
    转移所有权的两种方式:
    std::unique_lock<std::mutex> sbguard1(my_mutex1);
    std::unique_lock<std::mutex> sbguard2(std::move(sbguard1));
    
    方法2:返回所有权
    std::unique_lock<std::mutex> rtn_unique_lock()
    {
        std::unique_lock<std::mutex> tmpguard(my_mutex1);
        return tmpguard;
        //从函数返回一个局部的unique_lock对象是可以的;
        讲解过移动构造函数。返回这种局部对象tmpguard会导致系统生成临时
        unique_lock对象,并调用unique_lock的移动构造函数;
    }
    
    使用方法:
    std::unique_lock<std::mutex> sbguard = rtn_unique_lock();
    
    */
    
    /*
    单例设计模式:
    
    std::mutex resource_mutex;
    std::once_flag g_flag;//这是个系统定义的标记
    
    class MyCAS{
    private:
        MyCAS(){}
    private:
        static MyCAS *m_instance;
    public:
        static MyCAS* GetInstance()
        {
            if(m_instance == NULL)//双重锁定(双重检查)
            {
                std::unique_lock<std::mutex> mymutex(resource_mutex);//自动加锁
                if(m_instance == NULL)
                {
                    m_instance = new MyCAS();
                    static CG c1;
                }
            }
            return m_instance; 
        }
    
        class CG{
            public : 
                ~CG()
                {
                    if(MyCAS::m_instance)
                    {
                        delete MyCAS::m_instance;
                        MyCAS::m_instance = NULL;
                    }
                }
            
        };
    
        void func()
        {
            cout << "test" << endl;
        }
    };
    
    MyCAS* MyCAS::m_instance = NULL;
    使用:
    MyCAS *p = MyCAS::GetInstance();
    
    //std::call_once():C++11引入的函数,该函数的第二个参数是一个函数名a();
    //call_once()功能是能够保证函数a()只被调用一次;
    //call_once()具备互斥量这种能力,而且效率上,比互斥量消耗的资源更少;
    //call_once()需要 与一个标记结合使用,这个标记std::once_flag;其实once_flag是一个结构;
    call_once()就是通过这个标记来决定对应的函数a()是否执行,调用call_once()成功后,
    call_once()就把这个标记设置为一种已调用状态,那么对应的函数a(),就不会被调用了;
    
    通过修改以上代码,可以如下使用:
    std::call_once(g_flag,CreateInstance);两个线程同时执行到这里,其中一个
    线程要等待另一个线程执行完再执行;
    g_flag表示是否被执行过,如果执行过会被标记为1,接下来的线程不会再次执行CreateInstance();
    
    类中CreateIntance()如下:
    static void CreateInstance()
    {
        std::chrono::milliseconds dura(20000);
        std::this_thread::sleep_for(dura);
    
        m_instance = new MyCAS();
        static CG cl;
    }
    static MyCAS* GetInstance()
    {
        std::call_once(g_flag,CreateInstance);
        cout << "ok " << endl;
        return m_instance;
    }
    */
    
    /*
    一,条件变量std:: condition_variable,wait(),notify_one();
    线程A:等待一个条件满足;
    线程B:专门往消息队列中扔消息(数据)
    std::condition_variable实际上是一个类,是一个和条件相关的一个类,
    说白了就是等待一个条件达成。这个类是需要和互斥量配合工作,
    用的时候我们要生成这个类的对象;
    
    如何避免通过不断的判断来执行某些语句,而通过通知函数或者条件达成 后
    通知执行呢?
    
    具体代码如下:
    class A
    {
    public:
        void inMsgRecvQueue()
        {
            for(int i = 0; i < 10000; i++)
            {
                std::unique_lock<std::mutex> sbguard1(my_mutex1);
                msgRecvQueue.push_back(i);
                //尝试把wait()的线程唤醒,那么OutMsgRecvQueue()就被
                //唤醒了;
                my_cond.notify_one();
            }
        }
    
    
        void outMsgRecvQueue()
        {
            int command = 0;
            while(true)
            {
                std::unique_lock<std::mutex> sbguard1(my_mutex1);
                //wait()用来等一个东西
                //如果第二个参数lambda表达式是false,wait将解锁
                //互斥量,并堵塞到本行;
                //那堵塞到什么时候呢?堵塞到其他线程调用notify_one()
                //成员函数为止;
                //如果wait()没有第二个参数,my_cond.wait(sbguard1),那么
                //就和第二个参数表达式返回false的效果一样,wait()将
                //解锁互斥量,并堵塞到本行,堵塞到其他某个线程调用notify_one()
                //成员函数为止;
    //当其他线程用notify_one()将本wait(原来是睡着/赌塞)的状态
    唤醒后,wait就开始恢复干活了,恢复后wait干什么呢?
    a)wait不断地尝试重新获取互斥量锁,如果获取不到,那么流程就卡在
    wait这里等待获取,如果获取到了锁(等于加了锁),
    b)如果wait有第二个参数(lambda),就判断这个表达式,如果表达式为
    false,那么wait又对互斥量解锁。如果表达式为true,则wait返回,
    流程继续,(此时互斥锁是被锁着的);
    c)如果wait没有第二个参数,则wait返回,流程走下来;
    
    注意:只有wait()处于等待时,notify_one()才有效果,如果该线程正在
    处理其他事情时,不是卡在wait()时,此时notify_one()也会没有效果。所以
    不要想当然。以为只要notify_one()就可以。所以notify_one()要看其他线程是否
    正处于wait()状态下;
    notify_one():唤醒一个线程;
    notify_all():我们尝试把wait()的线程唤醒,执行完这行,
    那么outMsgRecvQueue()里面的wait就会被唤醒,唤醒之后的事情后续研究;
    
                //一个lambda就是一个可调用对象(函数)
                my_cond.wait(sbguard1,[this]{
                    if(!msgRecvQueue.empty())
                    {
                        return true;
                    }
                    return false;
            });
        }
    
        //流程执行都这里,这个互斥锁一定是被锁住的;
        command = msgRecvQueue.front();//返回第一个元素,但不检查元素是否存在;
        msgRecvQueue.pop_front();//移除第一个元素,但不返回;
        sbguard1.unlock();
        cout << "outMsgRecvQueue" << endl; 
    
    private:
        std::list<int> msgRecvQueue;
        std::mutex my_mutex1;
        std::condition_variable my_cond;//生成一个条件变量对象
    };
    */
    
    /*
    std::async,std::future创建后台任务并返回;
    希望线程返回一个结果;
    std::async是个函数模板,用来启动一个异步任务,
    启动起来一个异步任务后,他返回一个std::future对象,
    std::future
    启动一个异步任务,就是自动创建一个线程并开始执行对应的入口函数,
    他返回一个std::future对象,std::future对象里面就含有线程入口函数所
    返回的结果,(线程返回的结果),我们可以通过调用future对象的成员
    函数get()来获得结果;
    
    将来的意思,有人也称呼std::future提供了一种访问异步操作结果的机制,
    这个结果你可能没有办法马上拿到结果,线程执行完毕后你就能拿到结果;
    */
    
    /*
    int mythread()
    {
        cout << "mythread() " << std::this_thread::get_id() << endl;
        std::chrono::milliseconds dura(5000);
        std::this_thread::sleep_for(dura);
        cout << "mythread end" << "threadid=" << std::this_thread::get_id() << endl;
        return 6;
    }
    
    int main()
    {
        cout << "main" << "threadid=" << std::this_thread::get_id() <<endl;
        std::future<int>  result = std::async(mythread);
        cout << "continue" << endl;
        int def;
        def = 0;
        cout << result.get() << endl;
        //result.wait()等待线程结果返回,本身不返回结果值;
        cout << "I love china" << endl;
        return 0;
    }
    
    如果用类的成员函数作为线程的入口如何做呢?
    备注:
    A a;
    mythread:为类A的成员函数;
    int tmpar = 12;为整型参数;
    第二个参数是对象的引用,它能保证对象线程使用的是同一个对象;
    std::future<int>  result = std::async(&A::mythread,&a,tmpar);
    cout << result.get() << endl;//确保线程执行完毕;
    我们通过额外向std::async()传递一个参数,该参数的类型是std::launch类型(枚举类型)
    来达到一些特殊的目的;
    std::launch::deferred:表示线程入口函数调用被延迟到std::future的wait()或者
    get()函数调用时才执行;
    那如果wait()或者get()没有被调用,那么线程会执行吗?不会;
    除非调用wait()或者get();
    std::packaged_task:打包任务,把任务包装起来;
    是个类模板,它的模板参数是各种可调用对象;
    通过std::packaged_task来把各种可调用对象包装起来,
    方便将来作为线程入口函数;
    
    我们把函数mythread通过packaged_task包装起来;
    
    std::packaged_task<int(int)> mypt(mythread);
    线程直接开始执行,第二个参数作为线程入口函数的参数;
    std::thread t1(std::ref(mypt),1);
    t1.join();//等待线程执行完毕;
    std::future<int> result = mypt.get_future();//std::future对象里包含有线程入口函数的返回结果,
    这里result保存mythread的执行结果;
    cout << result.get() << endl;
    
    
    
    使用lambda表达式作为线程的入口函数:
    std::packaged_task<int(int)> mypt([](int mypar){
    cout << mypar << endl;
    cout << "mythread() start" << std::this_thread::get_id() << endl;
    std::chrono::milliseconds dura(5000);
    std::this_thread::sleep_for(dura);
    cout << "mythread() end" << "threadid=" << std::this_thread::get_id() << endl;
    return 0;
    });
    
    std::thread t1(std::ref(mypt),1);
    t1.join();
    std::future<int> result = mypt.get_future();
    cout << result.get() << endl;
    cout << "I love china" << endl;
    
    总结:packaged_task对象本身也是一个可调用对象,
    比如:
    mypt(106);
    std::future<int> result = mypt.get_future();
    cout << result.get() << endl;
    
    其他使用方法:
    
    vector<std::packaged_task<int(int)> > mytasks;
    mytasks.push_back(std::move(mypt));
    std::packaged_task<int(int)> mypt2;
    auto iter = mytasks.begin();
    mypt2 = std::move(*iter);
    
    mytasks.erase(iter);
    mypt2(123);
    std::future<int> result = mypt2.get_future();
    cout << result.get() << endl;
    
    std::promise ,类模板;
    通过在某个线程中给他赋值,然后我们可以再其他线程中,把这个值取出来;
    总结:通过promise保存一个值,在将来某个时刻我们通过把一个future
    绑定到这个promise上来得到这个绑定的值;
    
    void mythread(std::promise<int>&tmpp, int calc)
    {
        //做一系列复杂的操作
        calc++;
        calc* = 10;
        std::chrono::milliseconds dura(5000);
        std::this_thread::sleep_for(dura);
    
        int result = calc;
        tmpp.set_value(result);
        result;
    }
    
    void mythread2(std::future<int> &tmpf)
    {
        auto result = tmpf.get();
        cout << "mythread2 result" << result << endl;
        return;
    }
    int main()
    {
        std::promise<int> myprom;//声明一个std::promise对象myprom,保存的类型为int;
        std::thread t1(mythread,std::ref(myprom),180);
        t1.join();
    
        //获取结果值
        std::future<int> ful = myprom.get_future();
        auto result = ful.get();
        cout << "result=" << result << endl;
        cout << "i love china" << endl;
    }
    */
    
    /*
    std::future<int> result = std::async(mythread);
    std::future_status status = result.wait_for(std::chrono::seconds(1));
    if(status == std::future_status::timeout)//等待线程一秒钟,希望其返回;但是线程等待时间为5秒钟,因此超时;
    {
        //超时,表示线程未执行完成;
    }
    else if(status == std::future_status::ready)
    {
        //线程成功执行完毕;
    }
    else if(status == std::future_status::deferred)
    {
        //std::future<int> result = std::async(std::launch::deferred,mythread);线程被延迟执行;
        //async的第一个参数为::std::async(std::launch::deferred,mythread);时满足条件;
        //
    }
    */
    
    /*
    为什么第二次get这个future得到一个异常,主要是因为get函数的设计,是一个移动语义;
    std::shared_future:也是一个类模板;get()函数是复制数据;
    例如:
    std::future<int> result = mypt.get_future();//std::future,这个对象里含有线程入口函数
    test:
    bool isCanValue = result.valid();
    方法1//std::shared_future<int> result_s(std::move(result));
    方法2:
    std::shared_future<int> result_s(result.share());
    通过get_future()返回值直接构造了一个shared_future对象;
    std::shared_future<int> result_s(mypt.get_future());
    auto mythreadresult = result_s.get();
    mythreadresult = result_s.get();
    */
    
    /*
    原子操作:std::atomic;
    (3.2)原子操作概念引出范例;
    互斥量:多线程编程中,保护共享数据,锁,操作共享数据,开锁;
    有两个线程,对一个变量进行操作,这个线程读该变量值,另一个线程往
    这个变量中写值;
    
    
    大家可以把原子操作理解成一种,不需要用到互斥量加锁技术的多线程并发编程方式;
    原子操作:是在多线程中,不会被打断的,程序执行片段,
    原子操作,比互斥量效率上更胜一筹。互斥量的加锁一般是针对一个代码段(几行
    代码,而原子操作针对的一般都是一个变量,而不是一个代码段;
     一般是指不可分割的操作,要么完成,要么没完成,不可能是半完成状态;
    
     //int g_count = 0;
    我们封装了一个int类型的对象,可以像其他类型一样使用它;
     std::atomic<int> g_mycount = 0;
     使用方法可以如下:
     g_mycount++;
    
     一般用于计数或者统计,(累计发送出去了多少个数据包等);
     原子操作符:++,--,+=,-=,等;
     g_mycount = g_mycount + 1;//结果不对;
     std::async();
     std::thread()如果系统资源紧张,那么可能创建线程就会
     失败,那么执行std::thread()时整个程序可能崩溃;
     std::async():我们一般不叫创建线程,(解释async能够创建线程),
     我们一般叫它创建,一个异步任务;
     1)如果用std::launch::deferred来调用async会怎么样?
     deferred延迟调用,并且不创建新线程,延迟到future对象调用get()
     或者wait()时候才执行mythread,如果没有调用get(),wait()不会创建;
    2)std::launch::async:强制这个异步任务在新线程上执行,
    这意味着,系统必须要给我创建出新线程来运行mythread();
    3)std::launch::async | std::launch::deferred;
    这个|:意味着调用async的行为可能是创建新线程,并立即执行;
    或者是没有创建新线程并延迟到调用result.get()才开始执行任务入口函数;
    两者居其一;
    
    d)我们不带额外参数,只给async函数一个入口函数名;
    其实,默认值应该是std::launch::async | std::launch::deferred;
    换句话说,系统会自行决定是异步(创建新线程)还是同步(不创建新线程)
    方式运行;
    //自行决定是啥 意思?系统如何决定是异步(创建新线程)还是同步(不创建新线程)
    方式运行;
    
    std::async 和 std::thread区别:
    1,std::thread创建线程,如果系统资源紧张,创建线程失败,那么整个程序会报异常崩溃;
    2,std::thread创建线程的方式,如果线程返回值,你想拿到也不容易;
    3,std::async:创建异步任务;可能创建也可能不创建线程;并且async调用方法很容易拿到线程
    入口函数的返回值;
    //由于系统资源限制;
    1,如果用std::async,一般就不会报异常不会崩溃,因为如果系统资源紧张导致无法创建
    新线程的时候,std::async这种不加额外参数的调用,就不会创建新线程。
    而是后续谁调用了result.get()来请求结果;那么这个异步任务mythread就运行在执行这条
    get()语句所在的线程上;
    如果你强制std::async一定要创建新线程,那么就必须使用:std::launch::async;承受的代价是系统资源紧张时,程序崩溃;
    
    std::async不确定性问题的解决
    由于不带额外参数的std::async调用,让系统自行决定是否创建新线程;
    这个问题的焦点在于 std::future<int> result = std::async(mythread);写法;
    这个异步任务到底有没有推迟执行;(std::launch::async还是std::launch::deferred);
    std::future对象的wait_for函数,第10节讲过;
    
    定义开关:#define __WINON_
    #ifdef __WINON_
    ...
    CRITICAL_SECTION my_winsec;//windows中的临界区,非常类似C++11中的mutex;
    #endif
    
    使用临界区时需要先初始化:
    InitializeCriticalSection(&my_winsedc);//临界区初始化;
     
     然后使用:
     #ifdef __WINON_
        EnterCriticalSection(&my_winsec);
        msgRecvQueue.push_back(i);
        LeaveCriticalSection(&my_winsec);
    #else 
        my_mutex.lock();
        msgRecvQueue.push_back(i);
        my_mutex.unlock();
    #endif
    
    windows:多次进入临界区
    在同一线程中(不同的线程就会卡住等待),windows中的
    “相同的临界区变量”代表的临界区的进入,
    但是你调用了几次EnterCriticalSection,你就得调用几次LeaveCriticalSection(&my_winsec);
    C++11是不允许同一个线程的地方连续调用多次lock的;
    
    自动析构技术:
    std::lock_guard<std::mutex> sbguard(my_mutex);
    class CWinLock
    {
        public:
            CWinLock(CRITICAL_SECTION *pCritmp)
            {
                m_pCritical = pCritmp;
                EnterCriticalSection(m_pCritical);
            }
    
            ~CWinLock()
            {
                LeaveCriticalSection(m_pCritical);
            }
        private:
            CRITICAL_SECTION* m_pCritical;
    };
    
    使用方法: 
    CWinLock wlock(&my_winsec);
    
    std::mutex:独占互斥量,自己lock时别人lock不了;
    recursive_mutex:递归的独占互斥量;允许同一个线程,同一个
    互斥量多次被lock(),效率上比mutex要差一些;
    recursive_mutex:也有lock,也有unlock();
    考虑代码是否有 优化空间;
    递归次数据有限制,递归太多次可能报异常;
    带超时的互斥量std::timed_mutex和std::recursive_timed_mutex
    std::timed_mutex:是带超时功能的独占互斥量;
    try_lock_for():参数是一段时间,是等待一段时间;
    如果我拿到了锁,或者等待超时时间没有拿到锁,就走下来;
    try_lock_until():参数是一个未来的时间点,
    在这个未来的时间没到的时间内,如果拿到了锁,那么就走下来;
    如果时间到了,没拿到锁,程序流程也走下来;
    
    std::recursive_timed_mutex:带超时功能的递归独占互斥量;
    
    (1/1)补充一些知识;
    wait(),notify_oen(),notify_all();
    
    cout << atom << endl;//atom读是一个原子操作,整个这一行不是一个原则操作;
    class A
    {
        public:
            atomic<int> atm;
            A()
            {
                atm = 0;
                //auto atm2 = atm;//不允许;
                //atomic<int> atm3 = atm;//这种定义时初始化操作不允许,
    
                //load:以原子方式读atomic对象的值
                atomic<int> atm2(atm.load());//读
                auto atm3(atm.load());
                atm2.store(12);//原子操作写;
                atm2 = 12;
            }
    
    };
    
    
    线程池:
    场景:
    服务端程序等待线程连接,每来一个客户端,就创建一个新线程为该客户提供服务;
    1)网络游戏;两万个玩家,不可能给每个玩家创建个新线程,此程序写法在这种场景下不通;
    2)程序稳定性问题;
    在程序启动后,我一次性的创建好一定数量的线程,更好地放心,觉得程序代码更稳定;
    线程创建数量:
    
     */
    怕什么真理无穷,进一寸有一寸的欢喜。---胡适
  • 相关阅读:
    Springboot 配置Slf4j + Logback
    一步一步创建SpringCloud项目(二)—— 使用feign和ribbon调用微服务
    一步一步创建SpringCloud项目(一)——创建项目父工程、Eureka
    rabbitmq使用笔记
    docker部署rabbitmq
    Maven pom文件中dependency scope用法
    MySQL事务隔离级别总结
    docker安装redis
    RabbitMQ的消息确认机制
    centos7.6 下安装docker,docker compose
  • 原文地址:https://www.cnblogs.com/hujianglang/p/11626810.html
Copyright © 2011-2022 走看看