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)程序稳定性问题;
在程序启动后,我一次性的创建好一定数量的线程,更好地放心,觉得程序代码更稳定;
线程创建数量:

 */
怕什么真理无穷,进一寸有一寸的欢喜。---胡适
原文地址:https://www.cnblogs.com/hujianglang/p/11626810.html