muduo網路庫學習筆記(3):Thread類
muduo網路庫採用了基於物件的程式設計思想來封裝執行緒類。
類圖如下:
變數numCreated_表示建立的執行緒個數,型別為AtomicInt32,用到了我們上篇所說的原子性操作。
Thread類中還用到了CurrentThread類。
程式碼要點如下:
(1)執行緒識別符號
Linux中,每個程式有一個pid,型別為pid_t,由getpid()取得。Linux下的POSIX執行緒也有一個id,型別為pthread_t,由pthread_self()取得,該id由執行緒庫維護,其id空間是各個程式獨立的(即不同程式中的執行緒可能有相同的id)。Linux中的POSIX執行緒庫實現的執行緒其實也是一個程式(LWP:輕量級程式),只是該程式與主程式(啟動執行緒的程式)共享一些資源而已,比如程式碼段,資料段等。
有時候我們可能需要知道執行緒的真實pid。比如程式P1要向另外一個程式P2中的某個執行緒傳送訊號時,既不能使用P2的pid,更不能使用執行緒的pthread id,而只能使用該執行緒的真實pid,稱為tid。
函式gettid()可以得到tid,但glibc並沒有實現該函式,只能通過Linux的系統呼叫syscall來獲取。
return syscall(SYS_gettid)
因為使用系統呼叫開銷很大,所以我們需要對所獲取的tid做一個快取,防止每次都使用系統呼叫,從而提高獲取tid的效率。
程式碼片段:快取tid
檔名:CurrentThread.h
......
extern __thread int t_cachedTid; // 執行緒真實pid(tid)的快取
......
inline int tid()
{
if (t_cachedTid == 0)
{
cacheTid();
}
return t_cachedTid;
}
程式碼片段:cacheTid()
檔名:Thread.cc
void CurrentThread::cacheTid()
{
if (t_cachedTid == 0)
{
t_cachedTid = detail::gettid();
int n = snprintf(t_tidString, sizeof t_tidString, "%5d ", t_cachedTid);
// (void) n; 的用法是為了防止未使用變數n而出現編譯錯誤
assert(n == 6); (void) n;
}
}
(2)__thread關鍵字和POD型別
__thread是GCC內建的執行緒區域性儲存設施,存取效率可以和全域性變數相比。__thread變數在每一個執行緒有一份獨立實體,各個執行緒的值互不干擾。可以用來修飾那些帶有全域性性且值可能變,但是又不值得用全域性變數保護的變數。用一個例子來理解它的用法。
#include <pthread.h>
#include <iostream>
#include <unistd.h>
using namespace std;
//__thread int var = 5;
int var = 5;
void *worker1(void* arg);
void *worker2(void* arg);
int main()
{
pthread_t p1, p2;
pthread_create(&p1, NULL, worker1, NULL);
pthread_create(&p2, NULL, worker2, NULL);
pthread_join(p1, NULL);
pthread_join(p2, NULL);
return 0;
}
void *worker1(void* arg)
{
cout << ++var << endl;
}
void *worker2(void* arg)
{
cout << ++var << endl;
}
/**
* 使用__thread關鍵字,輸出為:
* 6
* 6
*
* 不使用__thread關鍵字,輸出為:
* 6
* 7
* /
**注:**__thread只能修飾POD型別,不能修飾class型別,因為無法自動呼叫建構函式和解構函式。
POD型別(plain old data)是指與C相容的原始資料型別,例如,結構體和整型等C語言中的型別就是 POD 型別,但帶有使用者定義的建構函式或虛擬函式的類則不是:
__thread可以用於修飾全域性變數、函式內的靜態變數,但是不能用於修飾函式的區域性變數或者class的普通成員變數。
另外,__thread變數的初始化只能用編譯器常量。
__thread string t_obj1(“hello”); // 錯誤,不能呼叫物件的建構函式
__thread string* t_obj2 = new string; // 錯誤,初始化必須用編譯期常量
__thread string* t_obj3 = NULL; // 正確,但是需要手工初始化並銷燬物件
(3)pthread_atfork()函式
#include <pthread.h>
int pthread_atfork(void (*prepare)(void),
void (*parent)(void),
void (*child)(void));
用法:呼叫fork時,內部建立子程式前在父程式中會呼叫prepare,內部建立子程式成功後,父程式會呼叫parent ,子程式會呼叫child。
(4)多執行緒與fork()
對於編寫多執行緒程式來說,最好不要再呼叫fork(),即不要編寫多執行緒多程式程式。因為Linux的fork()只克隆當前執行緒的thread of control ,不克隆其他執行緒。fork()之後,除了當前執行緒之外,其他執行緒都消失了,也就是說,不能一下子fork()出一個和父程式一樣的多執行緒子程式。
fork()之後子程式中只有一個執行緒,其他執行緒都消失了,這就造成一個危險的局面。其他執行緒可能正好位於臨界區之內,持有了某個鎖,而它突然死亡,再也沒有機會去解鎖了。如果子程式試圖再對同一個mutex加鎖,就會立刻死鎖。
一個在多執行緒程式裡fork造成死鎖的例子:
/*
死鎖的原因:
1. 執行緒裡的doit()先執行
2. doit執行的時候會給互斥量mutex加鎖
3. mutex的內容會原樣拷貝到fork出來的子程式中(在此之前,mutex變數的內容已經被執行緒改寫成鎖定狀態)
4. 子程式再次呼叫doit的時候,在給互斥量mutex加鎖的時候會發現它已經被加鎖,所以就一直等待,直到擁有該互斥體的程式釋放它(實際上沒有人擁有這個mutex鎖)
5. 執行緒的doit執行完成之前會把自己的mutex釋放,但這時的mutex和子程式裡的mutex已經是兩份記憶體.所以即使釋放了mutex鎖也不會對子程式裡的mutex造成什麼影響
*/
#include <stdio.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* doit(void* arg)
{
printf("%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("%d end doit\n",static_cast<int>(getpid()));
return NULL;
}
int main(void)
{
printf("%d enter 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("%d exit main\n",static_cast<int>(getpid()));
return 0;
}
執行結果如下:
相關文章
- muduo網路庫學習筆記(1):Timestamp類筆記
- muduo網路庫學習筆記(12):TcpServer和TcpConnection類筆記TCPServer
- muduo網路庫學習筆記(8):高效日誌類的封裝筆記封裝
- muduo網路庫學習筆記(2):原子性操作筆記
- muduo網路庫學習筆記(6):單例類(執行緒安全的)筆記單例執行緒
- muduo網路庫學習筆記(14):chargen服務示例筆記
- muduo網路庫學習之muduo_http 庫涉及到的類HTTP
- muduo網路庫學習筆記(11):有用的runInLoop()函式筆記OOP函式
- muduo網路庫學習之muduo_inspect 庫涉及到的類
- muduo網路庫學習筆記(13):TcpConnection生命期的管理筆記TCP
- muduo網路庫學習筆記(10):定時器的實現筆記定時器
- muduo網路庫學習筆記(7):執行緒特定資料筆記執行緒
- muduo網路庫學習筆記(9):Reactor模式的關鍵結構筆記React模式
- muduo網路庫學習筆記(4):互斥量和條件變數筆記變數
- muduo網路庫學習筆記(5):執行緒池的實現筆記執行緒
- muduo網路庫學習之EventLoop(四):EventLoopThread 類、EventLoopThreadPool 類OOPthread
- muduo網路庫Timestamp類
- muduo網路庫學習之Exception類、Thread 類封裝中的知識點(重點講pthread_atfork())Exceptionthread封裝
- muduo網路庫Exception異常類Exception
- muduo網路庫學習筆記(15):關於使用stdio和iostream的討論筆記iOS
- muduo網路庫學習之ThreadLocal 類、ThreadLocalSingleton類封裝知識點thread封裝
- muduo網路庫學習之EventLoop(七):TcpClient、ConnectorOOPTCPclient
- muduo網路庫學習之EventLoop(一):事件迴圈類圖簡介和muduo 定時器TimeQueueOOP事件定時器
- muduo網路庫學習之Timestamp類、AtomicIntegerT 類封裝中的知識點封裝
- muduo網路庫AtomicIntegerT原子整數類
- muduo網路庫學習之MutexLock類、MutexLockGuard類、Condition類、CountDownLatch類封裝中的知識點MutexCountDownLatch封裝
- muduo網路庫學習之BlockinngQueue類、ThreadPool 類、Singleton類封裝中的知識點BloCthread封裝
- 【OCP學習筆記】配置網路環境 -- 3筆記
- muduo網路庫學習之Logger類、LogStream類、LogFile類封裝中的知識點封裝
- 【學習筆記】網路流筆記
- [網路]NIO學習筆記筆記
- 網路流學習筆記筆記
- muduo網路庫使用心得
- muduo網路庫學習之EventLoop(六):TcpConnection::send()、shutdown()、handleRead()、handleWrite()OOPTCP
- muduo網路庫編譯安裝編譯
- Swift學習筆記(3)iOS 9 中的網路請求Swift筆記iOS
- 學習筆記16:殘差網路筆記
- Mudo C++網路庫第五章學習筆記C++筆記