muduo網路庫學習之Exception類、Thread 類封裝中的知識點(重點講pthread_atfork())
一、Exception類封裝
class Exception :
public std::exception
#include <execinfo.h>
int backtrace(void **buffer, int size); // backtrace 棧回溯,儲存各個棧幀的地址
char **backtrace_symbols(void *const *buffer, int size); // backtrace_symbols 根據地址,轉成相應的函式符號
int backtrace(void **buffer, int size); // backtrace 棧回溯,儲存各個棧幀的地址
char **backtrace_symbols(void *const *buffer, int size); // backtrace_symbols 根據地址,轉成相應的函式符號
// buffer 是指向一個陣列的指標,陣列存放的每一項是指向字串的指標
// backtrace_symbols 內部呼叫malloc 分配陣列空間,需要呼叫者自己釋放,但陣列指標指向的字串不需要呼叫者去釋放。
二、Thread類封裝
class Thread : boost::noncopyable
typedef boost::function<void ()> ThreadFunc;
具體實現分析見這裡。注意:結合CurrentThead.h 一起看,因為CurrentThead名稱空間內的一些函式是在
Thead.cc 裡面實現的,Thead類的一些成員函式也呼叫了CurrentThread名稱空間的一些函式。
1、獲取識別符號
pid --> getpid() //程式id
pthread_t --> pthread_self() //執行緒idtid--> gettid() //執行緒真實id
(1)、Linux中,每個程式有一個pid,型別pid_t,由getpid()取得。Linux下的POSIX執行緒也有一個id,型別 pthread_t,由pthread_self()取得,該id由執行緒庫維護,其id空間是各個程式獨立的(即不同程式中的執行緒可能有相同的id)。Linux中的POSIX執行緒庫實現的執行緒其實也是一個程式(LWP),只是該程式與主程式(啟動執行緒的程式)共享一些資源而已,比如程式碼段,資料段等。
(2)、有時候我們可能需要知道執行緒的真實pid。比如程式P1要向另外一個程式P2中的某個執行緒傳送訊號時,既不能使用P2的pid,更不能使用執行緒的pthread id,而只能使用該執行緒的真實pid,稱為tid。
(3)、有一個函式gettid()可以得到tid,但glibc並沒有實現該函式,只能通過Linux的系統呼叫syscall來獲取。
return syscall(SYS_gettid)
return syscall(SYS_gettid)
2、__thread,gcc內建的執行緒區域性儲存設施(每個執行緒有一份)
__thread只能修飾POD型別POD型別(plain old data),與C相容的原始資料,例如,結構體和整型等C語言中的型別是 POD 型別,但帶有使用者定義的建構函式或虛擬函式的類則不是
__thread string t_obj1(“simba”); // 錯誤,不能呼叫物件的建構函式__thread string* t_obj2 = new string; // 錯誤,初始化只能是編譯期常量若不是POD資料型別,但也想作為執行緒區域性儲存,可以使用執行緒特定資料TSD,參見以前的文章。__thread string* t_obj3 = NULL; // 正確
namespace CurrentThread
{
{
__thread int t_cachedTid = 0; //執行緒真實pid(tid)的快取,是減少系統呼叫::syscall(SYS_gettid),提高獲取tid的效率。__thread char t_tidString[32]; // tid 的字串表示__thread const char* t_threadName = "unknown"; //執行緒名稱const bool sameType = boost::is_same<int, pid_t>::value; //判斷型別是否相同BOOST_STATIC_ASSERT(sameType);
}
3、boost::is_same
const bool sameType = boost::is_same<int, pid_t>::value;
4、assert(n == 6); (void) n;
在release 版本下assert語句被忽略,那麼n是未使用的變數,由於編譯選項會把警告當作錯誤,導致編譯不通過。
5、pthread_atfork()
#include <pthread.h>
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
pthread_atfork()在fork()之前呼叫,當呼叫fork時,內部建立子程式前在父程式中會呼叫prepare,內部建立子程式成功後,父程式會呼叫parent ,子程式會呼叫child。
C++ Code
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#include <stdio.h>
#include <time.h> #include <pthread.h> #include <unistd.h> void prepare(void) { printf("pid = %d prepare ...\n", static_cast<int>(getpid())); } void parent(void) { printf("pid = %d parent ...\n", static_cast<int>(getpid())); } void child(void) { printf("pid = %d child ...\n", static_cast<int>(getpid())); } int main(void) { printf("pid = %d Entering main ...\n", static_cast<int>(getpid())); pthread_atfork(prepare, parent, child); fork(); printf("pid = %d Exiting main ...\n", static_cast<int>(getpid())); return 0; } |
simba@ubuntu:~/Documents/build/debug/bin$ ./pthread_atfork_test
pid = 4791 Entering main ...
pid = 4791 prepare ...
pid = 4791 parent ...
pid = 4791 Exiting main ...
simba@ubuntu:~/Documents/build/debug/bin$ pid = 4792 child ...
pid = 4792 Exiting main ...
simba@ubuntu:~/Documents/build/debug/bin$
pid = 4791 Entering main ...
pid = 4791 prepare ...
pid = 4791 parent ...
pid = 4791 Exiting main ...
simba@ubuntu:~/Documents/build/debug/bin$ pid = 4792 child ...
pid = 4792 Exiting main ...
simba@ubuntu:~/Documents/build/debug/bin$
因為父程式首先退出,故bash提到前臺,子程式輸出混雜在命令列。
在實際程式設計中,最好不要多執行緒多程式,兩者擇其一,比如在多執行緒程式中呼叫fork 容易出現死鎖,因為子程式複製父程式的時候包含狀態變化,如鎖的狀態如果被複制的時候是已經加鎖,那麼子程式想加鎖的時候就會死鎖,因為不是本程式程式加的鎖,解鎖就不從談起,一直處於等待中。看下面的例子:
C++ Code
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#include <stdio.h> #include <time.h> #include <pthread.h> #include <unistd.h> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void *doit(void *arg) { printf("pid = %d begin doit ...\n", static_cast<int>(getpid())); pthread_mutex_lock(&mutex); struct timespec ts = {2, 0}; nanosleep(&ts, NULL); pthread_mutex_unlock(&mutex); printf("pid = %d end doit ...\n", static_cast<int>(getpid())); return NULL; } int main(void) { printf("pid = %d Entering main ...\n", static_cast<int>(getpid())); pthread_t tid; pthread_create(&tid, NULL, doit, NULL); struct timespec ts = {1, 0}; nanosleep(&ts, NULL); if (fork() == 0) { doit(NULL); } pthread_join(tid, NULL); printf("pid = %d Exiting main ...\n", static_cast<int>(getpid())); return 0; } |
首先主執行緒先呼叫pthread_create()建立一個子執行緒執行doit(),doit()裡面先加鎖,睡眠2s; 主執行緒睡眠1s後呼叫fork(),子程式會複製父程式的記憶體映像,此時全域性變數mutex 處於加鎖的狀態,所以子程式自己的mutex也是加鎖的,此時子程式是獨立執行的,也去執行doit(),在裡面試圖加鎖,因為本來mutex已經加鎖,而且根本沒有人會來解鎖,所以子程式就會死鎖。
執行結果如下:
simba@ubuntu:~/Documents/build/debug/bin$ ./deadlock_test
pid = 4823 Entering main ...
pid = 4823 begin doit ...
pid = 4825 begin doit ...
pid = 4823 end doit ...
pid = 4823 Exiting main ...
simba@ubuntu:~/Documents/build/debug/bin$ ps aux | grep deadlock_test
simba 4825 0.0 0.0 11684 144 pts/0 S 06:09 0:00 ./deadlock_test
simba 4830 0.0 0.0 4392 848 pts/0 S+ 06:09 0:00 grep --color=auto deadlock_test
simba@ubuntu:~/Documents/build/debug/bin$
pid = 4823 Entering main ...
pid = 4823 begin doit ...
pid = 4825 begin doit ...
pid = 4823 end doit ...
pid = 4823 Exiting main ...
simba@ubuntu:~/Documents/build/debug/bin$ ps aux | grep deadlock_test
simba 4825 0.0 0.0 11684 144 pts/0 S 06:09 0:00 ./deadlock_test
simba 4830 0.0 0.0 4392 848 pts/0 S+ 06:09 0:00 grep --color=auto deadlock_test
simba@ubuntu:~/Documents/build/debug/bin$
可以看到Exiting main 只輸出了一次,子程式根本沒有從doit()出來,ps 一下可以發現pid=4825 的程式一直沒有退出,注意死鎖跟殭屍程式是不同的,殭屍程式是退出但佔據著資源還沒被清理,而死鎖是一直沒有退出程式。
此時可以用pthread_atfork() 來解決這個問題:
C++ Code
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
#include <stdio.h>
#include <time.h> #include <pthread.h> #include <unistd.h> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void *doit(void *arg) { printf("pid = %d begin doit ...\n", static_cast<int>(getpid())); pthread_mutex_lock(&mutex); struct timespec ts = {2, 0}; nanosleep(&ts, NULL); pthread_mutex_unlock(&mutex); printf("pid = %d end doit ...\n", static_cast<int>(getpid())); return NULL; } void prepare(void) { pthread_mutex_unlock(&mutex); } void parent(void) { pthread_mutex_lock(&mutex); } int main(void) { pthread_atfork(prepare, parent, NULL); printf("pid = %d Entering main ...\n", static_cast<int>(getpid())); pthread_t tid; pthread_create(&tid, NULL, doit, NULL); struct timespec ts = {1, 0}; nanosleep(&ts, NULL); if (fork() == 0) { doit(NULL); } pthread_join(tid, NULL); printf("pid = %d Exiting main ...\n", static_cast<int>(getpid())); return 0; } |
同樣的流程,但在執行fork() 建立子程式之前,先執行prepare(), 將子執行緒加鎖的mutex 解鎖下,然後為了與doit() 配對,在建立子程式成功後,父程式呼叫parent() 再次加鎖,這時父程式的doit() 就可以接著解鎖執行下去。而對於子程式來說,由於在fork() 建立子程式之前,mutex已經被解鎖,故複製的狀態也是解鎖的,所以執行doit()就不會死鎖了。
執行結果如下:
simba@ubuntu:~/Documents/build/debug/bin$ ./deadlock_test2
pid = 4905 Entering main ...
pid = 4905 begin doit ...
pid = 4908 begin doit ...
pid = 4905 end doit ...
pid = 4905 Exiting main ...
simba@ubuntu:~/Documents/build/debug/bin$ pid = 4908 end doit ...
pid = 4908 Exiting main ...
simba@ubuntu:~/Documents/build/debug/bin$
pid = 4905 Entering main ...
pid = 4905 begin doit ...
pid = 4908 begin doit ...
pid = 4905 end doit ...
pid = 4905 Exiting main ...
simba@ubuntu:~/Documents/build/debug/bin$ pid = 4908 end doit ...
pid = 4908 Exiting main ...
simba@ubuntu:~/Documents/build/debug/bin$
可以看到子程式也正常退出了。
參考:
muduo manual.pdf
《linux 多執行緒伺服器程式設計:使用muduo c++網路庫》
相關文章
- muduo網路庫學習之Timestamp類、AtomicIntegerT 類封裝中的知識點封裝
- muduo網路庫學習之BlockinngQueue類、ThreadPool 類、Singleton類封裝中的知識點BloCthread封裝
- muduo網路庫學習之MutexLock類、MutexLockGuard類、Condition類、CountDownLatch類封裝中的知識點MutexCountDownLatch封裝
- muduo網路庫學習之ThreadLocal 類、ThreadLocalSingleton類封裝知識點thread封裝
- muduo網路庫學習之Logger類、LogStream類、LogFile類封裝中的知識點封裝
- muduo網路庫學習筆記(3):Thread類筆記thread
- muduo網路庫Exception異常類Exception
- muduo網路庫學習筆記(8):高效日誌類的封裝筆記封裝
- muduo網路庫學習之muduo_http 庫涉及到的類HTTP
- muduo網路庫學習之muduo_inspect 庫涉及到的類
- muduo網路庫學習之EventLoop(四):EventLoopThread 類、EventLoopThreadPool 類OOPthread
- muduo網路庫學習筆記(1):Timestamp類筆記
- DispatcherDervlet類中重點方法講解
- muduo網路庫Timestamp類
- muduo網路庫學習筆記(12):TcpServer和TcpConnection類筆記TCPServer
- muduo網路庫學習之EventLoop(一):事件迴圈類圖簡介和muduo 定時器TimeQueueOOP事件定時器
- Laravel 小知識點之 HtmlString 類LaravelHTML
- Thread執行緒知識點講解thread執行緒
- Java中類的一些知識點Java
- muduo網路庫學習筆記(6):單例類(執行緒安全的)筆記單例執行緒
- 13封裝網路請求類庫封裝
- muduo網路庫AtomicIntegerT原子整數類
- jquery學習之重要知識點jQuery
- muduo網路庫學習之EventLoop(七):TcpClient、ConnectorOOPTCPclient
- 學習網路安全能掌握哪些知識點?網路安全技術學習
- C++中類相關知識點總結C++
- 物件與類_知識點筆記物件筆記
- HTML5學習重點知識:JavaScript概述HTMLJavaScript
- MyBatis知識點學習MyBatis
- Java知識點學習Java
- 校招面試知識點複習之計算機網路面試計算機網路
- Java類和物件知識點總結Java物件
- 靜態庫封裝之ComStr類封裝
- 靜態庫封裝之ComFile類封裝
- 靜態庫封裝之ComDir類封裝
- 財務知識點[網路]
- Python中關於Thread的一點小知識Pythonthread
- Bootstrap 個人學習知識點boot