• C++11并发与多线程


    0 引言 

    并发多线程是目的与手段之间的关系。并发是指大于等于2的活动同时发生。并发对软件行业的影响见下文。

    https://blog.csdn.net/hsutter/article/details/1435298

    (1)线程:线程是用来执行代码的一条通路,一个新的线程代表一条新的通路。

    (2)进程:运行起来的可执行程序,每个可执行文件运行起来,在操作系统的任务管理器中显示为一条进程。线程与进程的关系:每个进程都有一个唯一的主线程。

    (3)主线程:用来执行main函数的线程叫做主线程,主线程与进程唇齿相依。

    (4)软件并发:软件并发是指多个进程通过任务的快速切换,从呈现效果上来说展示为并发的一种状态。

    (5)硬件并发:指多核处理器可以真的同时执行多个任务。

    (6)并发的实现方式有两种:一种是多进程并发,一种是多线程并发。多进程并发要考虑通信。进程之间的通信方式有很多种,同一台电脑上分为:管道,文件,消息队列,共享内存等;不同电脑上可以采用socket通信技术。进程之间存在数据保护,进程之间的通信是一个复杂的事情。

    (7)多线程并发:一个进程中的所有线程共享地址空间,通过全局变量等在线程之间传递,其开销远远小于多进程。因此,一般并发优先考虑使用多线程技术。

    1 多线程编程

    (1)自己创建的函数要从一个初始函数开始执行。

    (2)join:加入和汇合的意思,一个thread对象执行join的效果是让主线程等待子线程执行完毕,再执行主线程。这也是传统的多线程程序推荐的方法,这样写出来的程序可以保证每个子线程都有有头有尾。

    (3)detach与join是相对的,是一个特例,一个thread对象执行了detach命令后,主线程与子线程分离,主线程不必等待子线程执行完毕再执行,主线程也不必非得等待子线程结束。detach之后,子线程被c++运行时库接管了。detach会使子线程失去控制,使用有风险。一旦detach了,不能再join了,否则系统会报告异常。

    (4)joinable: 判断线程对象是否可以join或者detach,一般可以join就可以detach,不能join就不能detach,所以没有detachable.

    2 子线程初始函数中的临时对象传递问题 

    (1)普通函数中的临时对象传递问题

    首先创建测试用例如下。
        void MyPrint(const int & temp_i, char* temp_ch) {
            std::cout <<temp_i<< std::endl;
            std::cout << ch << std::endl;
        }
    
        void TestMyPrint() {
            int i = 10;
            int &ref_i = i;
            char ch[] = "This is a test!";
            MyPrint(ref_i , ch);
        }
    在运行此用例时,利用vs中的快速监视功能,观察变量的值,发现:
    &i =
    0x0021f9c0 {10}
    &ref_i = 0x0021f9c0 {10}
    &temp_i = 0x0021f9c0 {10}
    可以得出结论:普通函数中,当形参为引用时,操作系统不会复制此变量。
    ch = 0x002ff838 "This is a test!"
    temp_ch = 0x002ff838 "This is a test!"
    可以得出结论:普通函数中,当形参为指针时,操作系统不会复制此变量。
    因此,采用引用和指针传递变量的效率会比创建其他变量要高,因为操作系统需要额外复制一份临时变量。

    (2)子线程的初始函数中的临时对象传递问题

        void MyPrint(const int & temp_i, char* temp_ch) {
            std::cout << temp_i << std::endl;
            std::cout << temp_ch << std::endl;
        }
      void TestTempObj()
        {
            int i = 10;
            int &ref_i = i;
            char ch[] = "This is a test!";
            std::thread temp_thread(MyPrint, ref_i, ch);
            temp_thread.join();
        }

    在运行此用例时,利用vs中的快速监视功能,观察变量的值,发现:
    &i = 0x0021f9b4 {10}
    &ref_i = 0x0021f9b4 {10}
    &temp_i = 0x002ab44c {10}
    可以得出结论,主线程在创建子线程时,其初始函数中的引用符号失效,引用变量仍然会单独复制一份。
    ch = 0x0021f990 "This is a test!"
    temp_ch = 0x0021f990 "This is a test!"
    可以得出结论,
    主线程在创建子线程时,其初始函数中的指针变量的值不变,不会单独复制一份。
    由于这个原因,在创建子线程时需要特别小心传递指针类型的变量,很容易出错。另外,传递引用类型变量时,如果发生了类型转换,也有可能会出错。

     (3)结论

    在子线程的初始函数中,建议采用如下规范:

    (a)若传递int这种简单类型的变量,建议采用值传递,降低风险;

    (b)若传递的是类对象,避免隐式类型转换,因为存在如下情况:

    class A{
       int m_a;
       A(int a):m_a(a){
       
       }
       ~A();     
    };
    
    void Print(const A& a){
         
    }
    int main()
    {
    int a_int = 10; std::thread my_thread(Print, a_int); // 此处的a_int可以隐式转化为A my_thread.detach();
    return 0;
    }

    在这种情况下,my_thread子线程与主线程分离,存在一种可能:即主线程已经执行完了,a_int对象已经被内存给释放了,但是子线程初始函数中的构造函数还未执行,使得该类型转换无法完成,程序出错。

    (c)子线程的初始函数传递的参数类型为类对象时,必须使用引用,如果不用的话,函数将调用三次拷贝构造函数,造成严重的浪费。

    (d)建议在操作子线程时,不使用detach,只是用join, 这样就不存在局部变量时效,导致对内存的非法引用问题。

    (4)临时对象构造时机的捕捉

        TA::TA(const int& i) : m_i(i) {
            std::cout << "TA(const int& i) 构造函数执行" << "线程的id号是: " << std::this_thread::get_id() << std::endl;
        }
    
        TA::TA(const TA& a) {
            this->m_i = a.m_i;
            std::cout << "TA(const TA& a) 拷贝构造函数执行" << "线程的id号是: " << std::this_thread::get_id() << std::endl;
        }
    
        TA::~TA() {
            std::cout << "~TA()析构函数执行" << "线程的id号是: " << std::this_thread::get_id() << std::endl;
        }

     (5)通过std::ref修饰子线程的初始函数中的参数对象,使得该值可以被修改;此时,编译器不再强行复制该引用值,而是直接采用引用的方式传递值,函数将可以在子线程中修改参数,并传回到主线程中。举例:

        void ChangeValue(const TA& a) {
            a.m_i = 18993838;
        }
    
        void TestMyPrint1() {
            std::cout << "主线程的id号是: " << std::this_thread::get_id() << std::endl;
            int i = 19;
            TA a(19);
            std::thread temp_thread(ChangeValue, std::ref(a));
            std::cout << a.m_i << std::endl;
            temp_thread.join();
        }
  • 相关阅读:
    redis-单线程为什么快
    redis-数据结构
    http-状态码
    事件绑定完整版2016/4/21
    焦点事件2016、4、21
    ++
    Bom2016/4/21
    添加以及删除className
    getByClassName2016/4/21
    动态添加
  • 原文地址:https://www.cnblogs.com/ghjnwk/p/12546577.html
Copyright © 2020-2023  润新知