C++并发编程

教程链接: https://study.163.com/course/introduction/1006067356.htm.

1-基本概念

  • 进程:
    一个可执行程序运行起来了,就叫创建了一个进程(进程就是运行 起来的可执行程序)。

  • 线程:

  1. 用来执行代码的。
  2. 每个进程都有一个主线程,这个主线程是唯一的;也就是一个进程只能有一个主线程。
  3. 当产生一个进程后,这个主线程就随着这个进程默默的启动起来了。
  4. 主线程与进程是唇齿相依的(main()函数执行主线程)。
  5. 每创建一个新线程,就可以在同一时刻,多干一件不同的事。
  • 多线程:

    线程并不是越多越好,每个线程都需要一个独立的堆栈空间,线程之间的切换要保存很多中间状态,切换会耗费本该属于程序运行的时间。

  • 并发(两个或对个任务同时进行)的实现方法:
    实现并发的手段:

    1. 通过多个进程实现并发:
      进程之间通信:

      1.同一电脑上:管道、文件、消息队列、共享内存。
      2.不同电脑上:socket通信技术。

    2. 在单独的线程中,创建多个线程实现并发(多线程并发):

      一个进程中的所有线程共享地址空间(共享内存)(全局变量、指针、引用都可以在线程之间传递)。

2-线程启动、结束、创建方法;join()/detach()

  • 程序运行起来,生成一个进程,该进程所属主线程开始自动运行。

  • 主线程从main()开始执行,那么我们自己创建的线程,也需要从一个函数开始运行(初始函数),一旦这个函数运行完毕,就代表着我们这个线程运行结束。

  • 整个进程是否执行完毕的标志是主线程是否执行完,如果主线程执行完毕了,就代表整个进程执行完毕了。

  • 此时,一般情况下,如果其他子线程还没有执行完毕,那么这些子线程也会被操作系统强行终止。

  • 所以,一般情况下,如果想保持子线程的运行状态的话,要让主线程一直保持运行状态,不要让主线程运行完毕。

  • 如何写多线程:

    1. 包含一个头文件<thread>

    2. 初始函数要写:void myprint(){……}

    3. main()中开始写代码

       thread mytobj(myprint);
       mytobj.join(); //阻塞主线程并等待myprint子线程执行完毕
      
    4. 说明:该程序有两个线程在跑,所以可以同时干两件事,即使一条线被堵住了,不影响另一条线运行。

  • 解释:

    1.1-thread:是标准库里的类,myprint:可调用对象

    thread mytobj(myprint); //1-创建了线程,线程执行起点(入口)myprint();2-myprint线程开始执行。
    

    1.2-join(); //阻塞主线程,让主线程等待子线程执行完毕,然后子线程和主线程汇合

      mytobj.join();//主线程阻塞到这里等待myprint()执行完,当子线程执行完毕,join()就执行完毕,主线程就继续往下走
    

    Note:子线程未执行完,主线程不能提前执行完。主线程等待子线程执行完毕后,自己才能最终退出。

    1.3-detach():传统多线程主线程要等待子线程执行完毕,然后自己再最后退出。

    detach:分离。//主线程执行你的,子线程执行我的,主线程不必等子线程运行结束,这并不影响子线程的执行。
    

    1.4-joinable():判断是否可以成功使用join()detach();返回true(可以join()detach())或false

  • 为什么引入detach()?

    我们创建了很多子线程,让主线程逐个等待子线程结束,这种编程方法不太好,所以引入了detach()。

    • 一旦detach()之后(mytobj.detach()),与这个主线程关联的thread对象就会失去与主线程的关联,此时子线程就会驻留在后台运行(主线程跟该子线程失去联系),子线程就相当于被C++运行时库接管了,当这个子线程执行完毕后,由运行时库负责清理相关的资源。(守护线程)
    • detach()使线程失去控制。一旦调用了detach(),就不能再用join()了,否则系统会报异常。
  • 其他创建线程的方法:

  • 用类对象(可调用对象),以及一个问题范例:

     class TA
     {
         public:
         	void operator()()//不能带参数
         	{......}
     };
     mian()
     {
     	TA ta;
     	thread myobj(ta);//ta:可调用对象
     }
    
    • Q:一旦调用了detach(),那主线程结束了,这里的ta对象还在吗?

    这个对象实际上是被 复制到线程中去的;所以执行完主线程后ta会被销毁,但是所复制 的TA对象依旧存在。所以,只要这个TA类对象里没有引用,没有指针,就不会产生问题。

  • 用lambda表达式

     auto mylamthread=[]{......};
      ......
     main()
     {
     	......
     	thread myobj(mylamthread);
     	......
     }
    

3-detach()陷阱分析

  • 线程传参、成员函数做线程参数

    1.传递临时变量作为线程参数(临时对象作为线程参数)

    • 要避免的陷阱:

      • 1.常数引用,字符指针(void myprint(const int &i, char *pmybuf)):

        问题:可能出现引用时,主线程已结束,引用参数地址空间已释放的情况。

      • 2.在形参中进行类型转换:

         void myprint(const int i, const string &pmybuf);
         main()
         {
         	......
         	thread mytobj(myprint, mvar, mybuf);
         	......
         }
         问题:可能出现主线程已结束,而子线程还未开始转换的情况。
        
        • 正确使用:

        thread mytobj(myprint, mvar,string(mybuf));在这里直接将mybuf转换成string对象,这是一个可以保证在线程中用肯定有效的对象;创建线程的同时构造临时对象的方法传递参数是可行的。

    • 总结:

    1. 若传递int 这种简单类型参数,建议都是值传递,不要用引用

    2. 如果传递类对象,避免隐式类型转换;全部都在创建线程这一行就构建临时对象来thread(myprint,var,A(mbuf));然后在函数参数里用引用来接,否则系统还会构造一次对象。

    3. 终极结论:建议不适用detach(),只使用join(),这样就不存在局部变量失效导致线程对内存的非法引用问题。

      线程id概念:id是个数字,每个线程(不管是主线程还是子线程)都对应一个数字,且都不相同,即不同的线程对应的线程id不同,可用std::this_thread::get_id()来获取。

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

  • 类对象

     void myprint(const A &pmybuf);
     main()
     {
     	   	A myobj(10);//A为类名
     	    thread mytobj(myprint,myobj);//将类对象(myobj)作为线程参数,子线程中修改m_i=10的值不会影响main()中的m_i。
     }
    

    std::ref()函数:
    thread mytobj(myprint,std::ref(myobj));//真引用,pmbuf地址与myobj地址一样(用detach()时可能就会有问题,主线程可能先执行完了)

  • 智能指针:

    void myprint(unique_ptr<int> pmbuf);//以智能指针作为参数
    main()
    {
    	unique_ptr<int> myp(new int(100));//智能指针
    	std::thread mytobj(myprint,std::move(myp));//传递智能指针
    }
    

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值