C++: C++11 成員函式作為pthread執行緒 (轉)
這種方式似乎也可以: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