1. 程式人生 > 其它 >C++: C++11 成員函式作為pthread執行緒 (轉)

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\n",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\n",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\n",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\n",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\n",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\n",m_name);
        m_count++;
        sleep(1);
    }
}

程式碼一下子完美啦~~

編譯A.cpp的時候,要這樣:

g++ -std=gnu++11 -c A.cpp -o A.o