• C++: C++11 成员函数作为pthread线程 (转)


    本文转自:https://zhou-yuxin.github.io/articles/2017/C++11%20%E6%88%90%E5%91%98%E5%87%BD%E6%95%B0%E4%BD%9C%E4%B8%BApthread%E7%BA%BF%E7%A8%8B/index.html

    这种方式似乎也可以:https://www.cnblogs.com/diegodu/p/4655036.html

    我自己也觉得这个标题讲得云里雾里的。事情是这样的:很多时候,我们希望一个class的某个成员函数能够作为一个线程来执行,就像Python中的threading库,只要把Thread()方法的target参数指向一个成员方法,比如self.__run,那么self.__run方法就会成为一个线程执行代码段。而pthread_create的原型是这样的:

    int pthread_create(pthread_t *thread,pthread_attr_t *attr,void *(*routine)(void *),void *arg);

    注意第三个参数routine是一个普通函数,而不能是一个成员函数。这不是废话嘛,不是普通函数怎么传进去。虽然很清晰,但是有时会破坏面向对象的思想。比如说Python中这么一段逻辑:

    import threading
    import time
    
    class A:
    
        def __init__(self,name):
            self.__name=name
            self.__count=0
            self.__running=True
            self.__thread=threading.Thread(target=self.__run)
            self.__thread.start()
    
        def count(self):
            return self.__count
    
        def stop(self):
            self.__running=False
            self.__thread.join()
    
        def __run(self):
            while self.__running:
                print(self.__name)
                self.__count+=1
                time.sleep(1)
    
    a=A('zjs')
    time.sleep(10)
    a.stop()
    print(a.count())

    很显然,因为成员方法可以作为线程体来执行,所以获得了如下好处:

    • 线程的变量传递非常方便,直接读写成员变量即可;
    • 线程体作为对象的一部分,可以访问对象的私有变量和方法。

    在C++11之前,如果要实现类似逻辑,一种写法是这样的:

    A.h

    #ifndef A_H
    #define A_H
    
    #include <pthread.h>
    
    class A
    {
    
    public:
        A(const char* name);
        int count();
        void stop();
    
    //不得不暴露
    public:
        const char* m_name;
        int m_count;
        bool m_running;
    
    private:
        pthread_t m_thread;
    
    };
    
    #endif

    A.cpp

    #include "A.h"
    
    #include <stdio.h>
    #include <unistd.h>
    
    static void* __run(void* arg)
    {
        A* a=(A*)arg;
        while(a->m_running)
        {
            printf("%s
    ",a->m_name);
            a->m_count++;
            sleep(1);
        }
    }
    
    A::A(const char* name)
    {
        m_name=name;
        m_count=0;
        m_running=true;
        pthread_create(&m_thread,0,__run,this);
    }
    
    int A::count()
    {
        return m_count;
    }
    
    void A::stop()
    {
        m_running=false;
        pthread_join(m_thread,0);
    }

    testA.cpp

    #include "A.h"
    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
        A a("zjs");
        sleep(10);
        a.stop();
        printf("%d
    ",a.count());
        return 0;
    }

    注意到之所以需要把A的三个成员变量设为public,是为了能够让__run()能够访问到。但这就破坏了封装,使得这三个变量对外暴露。为了提高封装性,另一种好一些的办法是这样的:

     A.h

    #ifndef A_H
    #define A_H
    
    #include <pthread.h>
    
    class A
    {
    
    public:
        A(const char* name);
        int count();
        void stop();
        //授权__A_run()能够访问私有变量
        friend void* __A_run(void* arg);
    
    private:
        const char* m_name;
        int m_count;
        bool m_running;
        pthread_t m_thread;
    
    };
    
    #endif

    A.cpp

    #include "A.h"
    
    #include <stdio.h>
    #include <unistd.h>
    
    void* __A_run(void* arg)
    {
        A* a=(A*)arg;
        while(a->m_running)
        {
            printf("%s
    ",a->m_name);
            a->m_count++;
            sleep(1);
        }
    }
    
    A::A(const char* name)
    {
        m_name=name;
        m_count=0;
        m_running=true;
        pthread_create(&m_thread,0,__A_run,this);
    }
    
    int A::count()
    {
        return m_count;
    }
    
    void A::stop()
    {
        m_running=false;
        pthread_join(m_thread,0);
    }

    testA.cpp

    #include "A.h"
    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
        A a("zjs");
        sleep(10);
        a.stop();
        // 然而__A_run()本身对外暴露了
        //__A_run(&a);
        printf("%d
    ",a.count());
        return 0;
    }

    可以看到,通过友元函数,可以使得线程体能够访问私有成员了。但是呢,友元函数本身是extern的,使得__A_run()本身对外暴露了,这使得外界可以手动调用__A_run,有一定的风险。不过函数暴露总归比变量暴露好得多。在C++11之前,封装性最好的办法,可能也就只能是这样了:在A.cpp中定义一个结构体,该结构体拥有和A一样的内存布局,并且A中成员变量连续分布,然后把A中第一个成员变量的地址传给线程体。比如这样:

    A.h

    #ifndef A_H
    #define A_H
    
    #include <pthread.h>
    
    class A
    {
    
    public:
        A(const char* name);
        int count();
        void stop();
    
    private:
        const char* m_name;
        int m_count;
        bool m_running;
        pthread_t m_thread;
    
    };
    
    #endif

    A.cpp

    #include "A.h"
    
    #include <stdio.h>
    #include <unistd.h>
    
    // 内存布局和A一样
    struct A_vars
    {
        const char* m_name;
        int m_count;
        bool m_running;
    };
    
    static void* __run(void* arg)
    {
        struct A_vars* a=(struct A_vars*)arg;
        while(a->m_running)
        {
            printf("%s
    ",a->m_name);
            a->m_count++;
            sleep(1);
        }
    }
    
    A::A(const char* name)
    {
        m_name=name;
        m_count=0;
        m_running=true;
        //第一个变量的地址传过去
        pthread_create(&m_thread,0,__run,&m_name);
        //pthread_create(&m_thread,0,__run,this);
    }
    
    int A::count()
    {
        return m_count;
    }
    
    void A::stop()
    {
        m_running=false;
        pthread_join(m_thread,0);
    }

    这样做,对外接口确实完美了,但是!太依赖底层的细节了,如果编译器对内存分布进行了优化,或者A有虚表,这种方法很可能就爆炸了!换句话说,通过牺牲稳定性、可移植性来换取封装性,貌似不太可取。

    好在C++ 11中出现了匿名函数,使得函数能够嵌套定义。

    A.h

    #ifndef A_H
    #define A_H
    
    #include <pthread.h>
    
    class A
    {
    
    public:
        A(const char* name);
        int count();
        void stop();
    
    private:
        void run();
    
    private:
        const char* m_name;
        int m_count;
        bool m_running;
        pthread_t m_thread;
    
    };
    
    #endif

    A.cpp

    #include "A.h"
    
    #include <stdio.h>
    #include <unistd.h>
    
    A::A(const char* name)
    {
        m_name=name;
        m_count=0;
        m_running=true;
        pthread_create(&m_thread,0,
            [](void* arg)
            {
                A* a=(A*)arg;
                a->run();
                return (void*)0;
            }
        ,this);
    }
    
    int A::count()
    {
        return m_count;
    }
    
    void A::stop()
    {
        m_running=false;
        pthread_join(m_thread,0);
    }
    
    void A::run()
    {
        while(m_running)
        {
            printf("%s
    ",m_name);
            m_count++;
            sleep(1);
        }
    }

    代码一下子完美啦~~

    编译A.cpp的时候,要这样:

    g++ -std=gnu++11 -c A.cpp -o A.o
  • 相关阅读:
    Protobuf, understand the hood
    Linux下Matlab崩溃的解决方法
    高德地图AMapUI is not defined
    微信小程序
    js字符数组转化为数字数组
    高德地图网页端js API
    es6 函数的扩展(尾递归看不太懂,不浪费时间了)
    es6 数值扩展(正则稍微看了下,以后用得着再细看)
    vue清空表单
    es6 变量的解析复制
  • 原文地址:https://www.cnblogs.com/yongdaimi/p/14894230.html
Copyright © 2020-2023  润新知