【笔记整理 - 多线程编程】
線程
編譯多線程程序時(shí),要注意參數(shù)
g++ -g 源文件.cpp -o 目標(biāo)文件 -lpthread
[root@localhost coding]# g++ -g test.cpp -o test -lpthread或者
g++ -g -o 目標(biāo)文件 源文件.cpp -lpthread
[root@localhost coding]# g++ -g -o test test.cpp -lpthread關(guān)于線程的注意事項(xiàng)(經(jīng)常忘)
1、雖然能按順序創(chuàng)建線程,但實(shí)際上的線程運(yùn)行順序是未知的。
2、主線程一退出,所有子線程都會(huì)退出。主線程要給子線程的運(yùn)行留下時(shí)間。
void* pth_foo(void* arg) {int i = (long)arg;printf("thread value : %d \n", i); }int main() {pthread_t pid[5];for(long i = 0; i < 5; ++i){if(pthread_create(&pid[i], NULL, pth_foo, (void*)(i + 1)) != 0)return -1;} }因?yàn)橹骶€程創(chuàng)建完子線程后就立即退出,并導(dǎo)致程序結(jié)束,使得子線程完全沒(méi)來(lái)得及執(zhí)行。
解決方法一:
讓主線程休眠一段時(shí)間。
sleep(1);解決方法二:
主線程調(diào)用pthread_exit函數(shù)退出,等到所有線程都執(zhí)行完畢后,進(jìn)程才結(jié)束。
pthread_exit(NULL); [root@localhost coding]# g++ -g test.cpp -o test -lpthread [root@localhost coding]# ./test thread value : 2 thread value : 3 thread value : 4 thread value : 5 thread value : 1在多線程中如果要輸出信息,最好還是用printf,如果使用cout,會(huì)出現(xiàn)多個(gè)線程的輸出混雜在一起的情況。
cout是個(gè)全局變量,多線程在使用共享資源時(shí)沒(méi)有上鎖,所以導(dǎo)致輸出混合的情況吧。
創(chuàng)建線程 pthread_create()
#include <pthread.h> int pthread_create(pthread_t*thread, const pthread_attr_t* attr, void*(*start_routine)(void*), void* arg);thread參數(shù)是新線程的標(biāo)識(shí)符,為一個(gè)整型。
attr參數(shù)用于設(shè)置新線程的屬性。給傳遞NULL表示設(shè)置為默認(rèn)線程屬性。
start_routine和arg參數(shù)分別指定新線程將運(yùn)行的函數(shù)和參數(shù)。start_routine返回時(shí),這個(gè)線程就退出了
返回值:成功返回0,失敗返回錯(cuò)誤號(hào)。
獲取當(dāng)前線程id pthread_self()
線程id的類型是thread_t,它只在當(dāng)前進(jìn)程中保證是唯一的,在不同的系統(tǒng)中thread_t這個(gè)類型有不同的實(shí)現(xiàn),調(diào)用pthread_self()可以獲得當(dāng)前線程的id。
終止線程 pthread_exit() pthread_cancel()
終止某個(gè)線程而不終止整個(gè)進(jìn)程,可以有三種方法:
- 從線程函數(shù)return。這種方法對(duì)主線程不適用,從main函數(shù)return相當(dāng)于調(diào)用exit。
- 線程調(diào)用pthread_exit()終止自己。
- 線程調(diào)用pthread_cancel()終止同一進(jìn)程中的另一個(gè)線程。
線程返回 pthread_exit()
#include <pthread.h> void pthread_exit(void *status);status:可以指向一個(gè)變量或數(shù)據(jù)結(jié)構(gòu),用于在線程結(jié)束時(shí)向外傳遞數(shù)據(jù),由用戶決定。
其它線程可以調(diào)用pthread_join()獲得這個(gè)指針。
注意!status指針不能指向線程的局部存儲(chǔ)對(duì)象,因?yàn)樵诰€程終止時(shí),所有局部存儲(chǔ)對(duì)象都會(huì)隨之銷毀。
例子:
#include <pthread.h> #include <errno.h>int val = 10;void* pth_foo(void* arg) {int i = 10;pthread_exit((void*)&val);// int *i = new int(15);// pthread_exit((void*)i); }int main() {pthread_t tid;int i = 0;if(pthread_create(&tid, NULL, pth_foo, NULL) != 0){printf("線程創(chuàng)建失敗。(%d:%s) \n", errno, strerror(errno));return -1;}// 線程函數(shù)傳出來(lái)的是個(gè)指針,所以也用指針接收int *pi; // 如果線程函數(shù)傳出來(lái)的是值,只要修改pi的類型就好了,下方代碼不用修改 // &pi為(void**),pi為(void*),剛好對(duì)應(yīng)“&val”——線程exit傳出的值~pthread_join(tid, (void**)&pi); // ~所以*pi就能得到全局變量val的數(shù)據(jù)printf("%d \n", *pi); }----------------------------------------- [root@localhost coding]# ./test 10 /* 如果 void* pth_foo(void* arg) {int i = 10;pthread_exit((void*)&i); } 則輸出的結(jié)果是0 */補(bǔ)充:在本例中,pthread_exit用return替代也能正常運(yùn)行。
二者區(qū)別(網(wǎng)絡(luò)):return會(huì)回到調(diào)用者,pthread_exit會(huì)終止線程。
再補(bǔ)充:return可用于所有函數(shù),而pthread_exit專門用于結(jié)束線程。
且return不會(huì)自動(dòng)調(diào)用線程清理函數(shù)。
線程取消 pthread_cancel()
#include <pthread.h> int pthread_cancel(pthread_t thread);thread:線程的標(biāo)識(shí)符
返回值:成功返回0,失敗返回錯(cuò)誤碼。
!補(bǔ)充
1、pthread_cancel后,thread內(nèi)容不變,至少用printf("%x\n", thread);輸出的結(jié)果在取消線程前后都是一致的。
2、對(duì)一個(gè)已經(jīng)取消的線程再次調(diào)用pthread_cancel,返回3。(對(duì)應(yīng)的錯(cuò)誤碼是3(No such process))
3、線程對(duì)pthread_cancel的默認(rèn)響應(yīng)狀態(tài)是PTHREAD_CANCEL_DEFERRED,線程運(yùn)行到取消點(diǎn)后才退出。
pthread_setcancelstate
(是否響應(yīng))
子線程可以通過(guò)調(diào)用pthread_setcancelstate,設(shè)置對(duì)pthread_cancel請(qǐng)求的響應(yīng)方式。
int pthread_setcancelstate(int state, int *oldstate); // 將線程的響應(yīng)方式設(shè)置為state,通過(guò)oldstate返回舊狀態(tài)。PTHREAD_CANCEL_ENABLE:響應(yīng)取消。
PTHREAD_CANCEL_DISABLE:不響應(yīng)。
pthread_setcanceltype
(如何響應(yīng))
int pthread_setcanceltype(int type, int *oldtype);pthread_setcanceltype設(shè)置線程的取消方式。
PTHREAD_CANCEL_ASYNCHRONOUS:異步取消,立即將線程取消;
PTHREAD_CANCEL_DEFERRED:推遲取消,線程運(yùn)行到**取消點(diǎn)(一些特定函數(shù))**后才取消。(取消點(diǎn):APUE P.363)
pthread_testcancel
調(diào)用這個(gè)函數(shù)時(shí),如果有某個(gè)取消請(qǐng)求處于掛起狀態(tài),且取消沒(méi)有設(shè)置為無(wú)效,那么線程就會(huì)被取消。
線程等待 pthread_join()
#include <pthread.h> int pthread_join(pthread_t thread, void **status);thread:調(diào)用該函數(shù)的線程將掛起等待,直到id為thread的線程終止。
status:
1、等待的線程通過(guò)return返回
? status所指向的地址存放的是線程函數(shù)的返回值。
2、等待的線程被其它線程調(diào)用函數(shù)pthread_cancel停止
? status所指向的地址存放的是常數(shù)PTHREAD_CANCELED。
3、等待的線程自己調(diào)用pthread_exit終止
? 可通過(guò)status獲取線程終止時(shí)保存的數(shù)據(jù)。如果不需要改數(shù)據(jù),傳入NULL。
線程函數(shù)退出后。exit時(shí)傳入?yún)?shù)保存了數(shù)據(jù),就能通過(guò)status獲取這個(gè)數(shù)據(jù)。
如果等待的線程調(diào)用pthread_exit時(shí)傳入?yún)?shù)保存了數(shù)據(jù),就能通過(guò)status獲取這個(gè)數(shù)據(jù)。
如果有多個(gè)線程調(diào)用pthread_join獲取同一個(gè)線程的執(zhí)行結(jié)果,則只有一個(gè)線程能得到結(jié)果,其余線程都將執(zhí)行失敗。
(3、)的例子:
... // 已知線程函數(shù)會(huì)用pthread_exit保存一個(gè)int類型變量int val;// 傳入變量val的地址pthread_join(tid, (void**)&val);// 函數(shù)返回后,變量val指向得到pthread_exit保存的數(shù)據(jù)printf("%d \n", val);返回值:成功返回0,失敗返回錯(cuò)誤碼(可以當(dāng)做int用)。可能出現(xiàn)的錯(cuò)誤碼:
| EDEADLK | 可能引起死鎖,比如2個(gè)線程互相針對(duì)對(duì)方調(diào)用pthread_join,或針對(duì)自身調(diào)用pthread_join |
| EINVAL | 目標(biāo)線程是不可回收的(分離狀態(tài)),或已有其它線程在回收該目標(biāo)線程 |
| ESRCH | 目標(biāo)線程不存在 |
線程的結(jié)合、分離概念(線程資源回收)
在任何一個(gè)時(shí)間點(diǎn)上,線程是可結(jié)合的(joinable)或者是分離的(detached)。
一個(gè)可結(jié)合的線程能夠被其他線程收回其資源和殺死。在被其他線程回收之前,它的存儲(chǔ)器資源(例如棧)不釋放。(默認(rèn)情況下線程的創(chuàng)建都是可結(jié)合的)
一個(gè)分離的線程是不能被其他線程回收或殺死,它的存儲(chǔ)器資源在它終止時(shí)由系統(tǒng)自動(dòng)釋放。
如果一個(gè)可結(jié)合線程結(jié)束運(yùn)行但沒(méi)有被join,會(huì)導(dǎo)致部分資源沒(méi)有被回收,所以創(chuàng)建線程者應(yīng)該調(diào)用pthread_join來(lái)等待線程運(yùn)行結(jié)束,并得到線程的退出代碼,回收其資源。
在調(diào)用pthread_join后,如果該線程沒(méi)有運(yùn)行結(jié)束,調(diào)用者會(huì)被阻塞。如何解決這種情況?
答:將等待的線程設(shè)置為分離狀態(tài)。也就不需要再調(diào)用pthread_join等待該線程。
分離線程 pthread_detach()
#include <pthread.h> int pthread_detach(pthread_t thread);thread:線程id
返回值:函數(shù)成功,返回0;失敗,返回錯(cuò)誤碼。
能在主線程中調(diào)用,或子線程中調(diào)用都可以。重要的是要傳入正確的線程id。
線程清理 pthread_cleanup_push/pop
線程可以安排它退出時(shí)需要調(diào)用的函數(shù)。退出函數(shù)可以建立多個(gè),記錄在棧中。
void pthread_cleanup_push(void (*routine)(void *), void *arg);routine:函數(shù)名
arg:參數(shù)
void pthread_cleanup_pop(int execute);execute:傳入非0參數(shù),彈出并執(zhí)行;傳入0,只彈出,不執(zhí)行。
線程函數(shù)調(diào)用pthread_exit時(shí),會(huì)按出棧的順序執(zhí)行所有清理函數(shù)。使用return退出的線程函數(shù)不會(huì)執(zhí)行清理函數(shù)。
鎖 !
http://c.biancheng.net/thread/vip_8615.html
大概描述:互斥鎖、信號(hào)量、條件變量、讀寫鎖(還有自旋鎖、屏障等)
- 互斥鎖:只允許一個(gè)線程進(jìn)入臨界區(qū);
- 信號(hào)量:允許n個(gè)線程進(jìn)入臨界區(qū);
- 條件變量:當(dāng)某線程滿足特定條件后進(jìn)入臨界區(qū),通常(幾乎是必須)和互斥鎖配合使用;
- 讀寫鎖:大多數(shù)線程能讀臨界區(qū),少數(shù)線程能寫臨界區(qū)。允許同時(shí)有多個(gè)讀者進(jìn)入,只允許1個(gè)作者進(jìn)入;有讀者時(shí)不能有作者,有作者時(shí)不能有讀者。
補(bǔ)充:條件變量所謂的“滿足特定條件后進(jìn)入臨界區(qū)”是由代碼結(jié)構(gòu)實(shí)現(xiàn)的,即它自身并沒(méi)有提供實(shí)現(xiàn)該功能的函數(shù)。
大概的代碼結(jié)構(gòu):一個(gè)線程到進(jìn)入?yún)^(qū)調(diào)用pthread_cond_wait等待,另一個(gè)線程達(dá)到滿足的條件后調(diào)用pthread_cond_signal解鎖。
所謂的讀寫鎖其實(shí)就是提供了2個(gè)上鎖的方式而已,具體的讀和寫動(dòng)作還是得由用戶自覺(jué)操作。
線程同步(鎖)
互斥變量為pthread_mutex_t類型。初始化時(shí)可以設(shè)置為常量PTHREAD_MUTEX_INITIALIZER(只適用于靜態(tài)分配的互斥量)或用pthread_mutex_init()初始化。
書(shū)上的例子
**1、**一個(gè)被互斥鎖(作為成員變量)保護(hù)的結(jié)構(gòu)foo,P.322:
#include <stdlib.h> #include <pthread.h>struct foo {int f_count;pthread_mutex_t f_lock;int f_id; }foo* foo_alloc(int id) {foo *fp;if(fp = (foo*)malloc(sizeof(foo)) != NULL){fp->f_count = 1;fp->id = id;if(pthread_mutex_init(&fp->f_lock, NULL) != 0){free(fp);return 0;}}// 繼續(xù)初始化操作 }void foo_hold(foo* fp) {pthread_mutex_lock(&fp->f_lock);fp->f_count++;pthread_mutex_unlock(&fp->f_lock); }void foo_rele(foo* fp) {pthread_mutex_lock(&fp->f_lock);if(--fp->f_count == 0){pthread_mutex_unlock(&fp->f_lock);pthread_mutex_destroy(&fp->f_lock);free(fp);}elsepthread_mutex_unlock(&fp->f_lock); }**2、**在程序中使用多個(gè)foo對(duì)象,多個(gè)foo對(duì)象用哈希表組織,增加一個(gè)互斥鎖2用于保護(hù)哈希表。P.323。
**3、**訪問(wèn)foo結(jié)構(gòu)的成員變量f_cout也使用互斥鎖2進(jìn)行保護(hù)。P.325。兩種用途使用相同的鎖(增加了鎖的粒度),簡(jiǎn)化了代碼結(jié)構(gòu)。
線程與信號(hào)
外界信號(hào)不會(huì)中斷子線程的運(yùn)行(除非是終止進(jìn)程的信號(hào))。
在多線程程序中,捕獲信號(hào)的函數(shù)放在哪都一樣,通常放在主函數(shù)中。
多線程程序中,在任一線程中調(diào)用signal或sigaction都會(huì)改變所有信號(hào)的信號(hào)處理函數(shù)。
主線程向子線程發(fā)送信號(hào)用pthread_kill函數(shù)。
多線程服務(wù)器的退出
退出信號(hào)(2 和 15)處理函數(shù)的流程:
1、關(guān)閉監(jiān)聽(tīng)socket;
2、用pthread_cancel終止所有子線程;
3、釋放資源(IO、文件、內(nèi)存等);
4、子線程執(zhí)行清理函數(shù)(在里面關(guān)閉通信socket);
多線程服務(wù)器中,子線程通常都是分離狀態(tài)的,且通常都是立即取消狀態(tài)。
I/O復(fù)用
《APUE》中的標(biāo)題是“I/O多路轉(zhuǎn)接”
需求背景
1、一些函數(shù),如read、accept在被調(diào)用時(shí),會(huì)阻塞調(diào)用函數(shù)的線程。
read(文件描述符, buf, bufsize); accept(套接字描述符, socketaddr, socketlen);2、可用的描述符數(shù)量可能會(huì)小于需求數(shù)量,例如要打開(kāi)很多個(gè)文件、要響應(yīng)很多個(gè)客戶端的連接等。
使用I/O多路轉(zhuǎn)接技術(shù),先構(gòu)造一張感興趣的描述符列表,然后調(diào)用一個(gè)函數(shù),直到這些描述符中的一個(gè)已經(jīng)準(zhǔn)備好響應(yīng)操作后,函數(shù)返回。
select
#include <sys/select.h>int select(int maxfdp1, fd_set* restrict readfds, fd_set* restrict writefds, fd_set* restruct execptfds, struct timeval* restruct tvptr);返回值:準(zhǔn)備就緒的描述符數(shù)目;超時(shí)——0;出錯(cuò)——-1。從后向前介紹參數(shù)
tvptr:等待時(shí)間,timeval結(jié)構(gòu),2個(gè)成員:time_t tv_sec、long tv_nsec分別表示s和ns。
struct timeval {time_t tv_sec; //秒suseconds_t tv_usec; //微秒 1ms = 10^(-6)s };? tvptr == NULL:永久等待,直到一個(gè)描述符已經(jīng)準(zhǔn)備好,或捕捉到一個(gè)信號(hào)。捕捉到信號(hào)會(huì)使函數(shù)返回-1,errno設(shè)置為EINTR
? tvptr->tv_sec == 0 && tvptr->tv_use c== 0:不等待,立即測(cè)試所有指定的描述符并返回。
? tvptr->tv_sec != 0 && tvptr->tv_usec != 0:等待指定的時(shí)間。當(dāng)指定的任一描述符準(zhǔn)備好,或超時(shí)時(shí)返回。
readfds、writefds、execptfds:指向描述符集的指針。描述符集為fd_set類型,基本上可認(rèn)為是一個(gè)很大的字節(jié)數(shù)組。
fd_set相關(guān)的操作函數(shù)
#include <sys/select.h>int FD_ISSET(int fd, fd_set *fdset); 返回值:如果fd在字符集中,返回非0值;否則返回0。void FD_CLR(int fd, fd_set *fdset);void FD_SET(int fd, fd_set *fdset);void FD_ZERO(fd_set *fdset);從視頻作者給出的例子看,select關(guān)心的描述符集中有描述符準(zhǔn)備好了就會(huì)返回,然后下方代碼就要用
for(i=0; i<maxfdp; ++i ) {...FD_ISSET(i, 字符集);... }的方式對(duì)范圍內(nèi)的所有描述符進(jìn)行測(cè)試。如果被打開(kāi)的描述符是想要的,就執(zhí)行相關(guān)操作。
**注意!**所謂的“被打開(kāi)的描述符”是在調(diào)用select函數(shù)前,由程序員自己通過(guò)調(diào)用FD_SET函數(shù)來(lái)設(shè)置的。
maxfdp1:最大描述符編號(hào)值+1。即指定描述符集的右區(qū)間。
[0, maxfdp1)
3個(gè)描述符集
readfds:讀,常用。writefds:寫,只有在輸出緩沖區(qū)滿時(shí)才會(huì)被阻塞,很少會(huì)遇到。execptfds:在網(wǎng)絡(luò)編程中用不到。pselect
添加了信號(hào)屏蔽參數(shù),但很少用到,因?yàn)橛衅渌姆绞狡帘涡盘?hào)。
select水平觸發(fā)
如果一個(gè)標(biāo)識(shí)符的事件沒(méi)有被處理完,select會(huì)再次報(bào)告該事件。(例如沒(méi)有將客戶端傳送到的數(shù)據(jù)一次讀完)
select 缺點(diǎn)
1、默認(rèn)支持的描述符數(shù)量只有1024,可以修改,但數(shù)量越多,效率越低。
2、每次確認(rèn)描述符都要遍歷select。
代碼(已運(yùn)行,能響應(yīng)多個(gè)客戶端)
#include "CTcpServer.h" #include <stdio.h> #include <string.h> #include <unistd.h> #include <netdb.h> #include <stdlib.h> #include <signal.h> #include <pthread.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/select.h>CTcpServer g_TcpServer;// 處理SIGINT和SIGTERM信號(hào) void EXIT(int sig) {printf("程序退出,信號(hào)值=%d \n", sig);close(g_TcpServer.m_listenfd);exit(0); }int main() {if (g_TcpServer.InitServer(5000) == false){printf("服務(wù)端初始化失敗,程序退出。\n");return -1;}fd_set readfdset;int maxfd;int listensock = g_TcpServer.m_listenfd;FD_ZERO(&readfdset);FD_SET(listensock, &readfdset);maxfd = listensock;while (1){fd_set tmpfdset = readfdset;int infds = select(maxfd + 1, &tmpfdset, NULL, NULL, NULL);if (infds < 0){printf("select() failed. \n");perror("select()");break;}if (infds = 0){printf("select() timeout. \n");continue;}for (int eventfd = 0; eventfd <= maxfd; ++eventfd){if (FD_ISSET(eventfd, &tmpfdset) <= 0)continue;if (eventfd == listensock){sockaddr_in client;socklen_t len = sizeof(client);int clientsock = accept(listensock, (sockaddr*)&client, &len);if (clientsock < 0){printf("accept() failed\n");continue;}printf("client(socket = %d) connect success.\n", clientsock);FD_SET(clientsock, &readfdset);if (maxfd < clientsock) maxfd = clientsock;continue;}else{char strbuffer[1024];memset(strbuffer, 0, sizeof(strbuffer));ssize_t isize = read(eventfd, strbuffer, sizeof(strbuffer));if (isize <= 0){printf("client(evenfd = %d) disconnected. \n", eventfd);close(eventfd);FD_CLR(eventfd, &readfdset);if (eventfd == maxfd){for (int i = maxfd; i > 0; --i)if (FD_ISSET(i, &readfdset)){maxfd = i; break;}printf("maxfd = %d, update. \n", maxfd);}continue;}printf("recv(eventfd = %d, size = %d):%s \n)", eventfd, isize, strbuffer);write(eventfd, strbuffer, strlen(strbuffer));}}}return 0; }問(wèn)題(似乎已解決)
作者代碼中調(diào)用select時(shí),要先創(chuàng)建一個(gè)fd_set類型的臨時(shí)變量tmpfdset,讓該臨時(shí)變量參與select函數(shù),在遍歷查找有事件響應(yīng)的標(biāo)識(shí)符時(shí),使用的也是臨時(shí)變量tmpfdset。
fd_set tmpfdset = readfdset;int infds = select(maxfd+1, &tmpfdset, NULL, NULL, NULL);...for(int eventfd = 0; eventfd <= maxfd; ++eventfd){if(FD_ISSET(eventfd, &tmpfdset) <= 0)continue;if(eventfd == listensock)...}經(jīng)過(guò)自己的粗略檢查(在select前后檢查tmpfdset標(biāo)識(shí)符4的值)得出的結(jié)論:
似乎select在返回時(shí),會(huì)將有事件的描述符以外的所有描述符都清零,所以使用for循環(huán)從0開(kāi)始遍歷一定且只會(huì)遇到發(fā)生了事件的那個(gè)描述符。
嘗試過(guò)直接將readfdset傳入select,結(jié)果服務(wù)端只能響應(yīng)第一個(gè)連入的客戶端。
驗(yàn)證代碼:
#define CHECK(x) printf("readfdset[%d] is %d \n", x, FD_ISSET(x, &tmpfdset))...fd_set tmpfdset = readfdset;CHECK(4); // 經(jīng)過(guò)幾次實(shí)踐,已知描述符4一定對(duì)應(yīng)第一個(gè)連接的客戶端socketint infds = select(maxfd+1, &tmpfdset, NULL, NULL, NULL);...for(int eventfd = 0; eventfd <= maxfd; ++eventfd){if(FD_ISSET(eventfd, &tmpfdset) <= 0)continue;CHECK(4);if(eventfd == listensock)...}結(jié)果:第1個(gè)客戶端的對(duì)應(yīng)描述符是4
... ----------------------- readfdset[4] is 1 // 調(diào)用select之前 readfdset[4] is 0 // 調(diào)用select之后 recv(eventfd = 5, size = 3): 345 ----------------------- readfdset[4] is 1 readfdset[4] is 1 recv(eventfd = 4, size = 3): qwe如果不是第1個(gè)客戶端的事件導(dǎo)致select返回,則select返回后,對(duì)應(yīng)的FD_ISSET(客戶端1, tmpfdset)會(huì)返回0。
poll
#include <poll.h>int poll(struct pollfd fdarray[]. nfds_t nfds, int timeout);返回值:成功——準(zhǔn)備就緒的描述符數(shù)目;超時(shí)——返回0;出錯(cuò)——返回-1nfds:就是select的maxfdp1。
struct pollfd {inf fd; // 文件標(biāo)識(shí)符。short events; // 期待標(biāo)識(shí)符上發(fā)生的事件short revents; // 標(biāo)識(shí)符發(fā)生事件后的返回值 }fd:若設(shè)置為-1,則表示忽略events,且revents返回0。
events:若設(shè)置為0,則忽略所有fd發(fā)生的事件,且revents返回0。輸入的參數(shù)似乎可以用“|”連接。
revents:是個(gè)輸出參數(shù),值由內(nèi)核填充,表示發(fā)生的事件。
例子中的代碼結(jié)構(gòu)與select基本一致。
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <poll.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/fcntl.h>// ulimit -n #define MAXNFDS 1024// 初始化服務(wù)端的監(jiān)聽(tīng)端口。 int initserver(int port);int main(int argc,char *argv[]) {...// 初始化服務(wù)端用于監(jiān)聽(tīng)的socket。int listensock = initserver(atoi(argv[1]));printf("listensock=%d\n",listensock);...int maxfd; // fds數(shù)組中需要監(jiān)視的socket的大小。struct pollfd fds[MAXNFDS]; // fds存放需要監(jiān)視的socket。// ------------- 等價(jià)于FD_ZERO() -------------for (int ii=0;ii<MAXNFDS;ii++) fds[ii].fd=-1; // 初始化數(shù)組,把全部的fd設(shè)置為-1。// 把listensock添加到數(shù)組中。fds[listensock].fd=listensock;fds[listensock].events=POLLIN; // 有數(shù)據(jù)可讀事件,包括新客戶端的連接、客戶端socket有數(shù)據(jù)可讀和客戶端socket斷開(kāi)三種情況。maxfd=listensock;while (1){ // ------------- 阻塞 -------------int infds = poll(fds, maxfd+1, 5000);// 返回失敗。if (infds < 0){printf("poll() failed.\n"); perror("poll():"); break;}// 超時(shí)。if (infds == 0){printf("poll() timeout.\n"); continue;}// 檢查有事情發(fā)生的socket,包括監(jiān)聽(tīng)和客戶端連接的socket。// 這里是客戶端的socket事件,每次都要遍歷整個(gè)集合,因?yàn)榭赡苡卸鄠€(gè)socket有事件。for (int eventfd=0; eventfd <= maxfd; eventfd++){if (fds[eventfd].fd<0) continue;// ------------- 與select略有不同,這里先檢查“.revents”事件類型 -------------if ((fds[eventfd].revents&POLLIN)==0) continue; // ------------- 未知 -------------fds[eventfd].revents=0; // 先把revents清空。// ------------- “.f”非零,且“.revents&POLLIN”非零,再檢查匹配標(biāo)識(shí)符 -------------if (eventfd==listensock){ // ------------- 下文內(nèi)容與select基本相同 -------------// 如果發(fā)生事件的是listensock,表示有新的客戶端連上來(lái)。struct sockaddr_in client;socklen_t len = sizeof(client);int clientsock = accept(listensock,(struct sockaddr*)&client,&len);if (clientsock < 0){printf("accept() failed.\n"); continue;}printf ("client(socket=%d) connected ok.\n",clientsock);if (clientsock>MAXNFDS){ printf("clientsock(%d)>MAXNFDS(%d)\n",clientsock,MAXNFDS); close(clientsock); continue;}// ------------- poll登記新標(biāo)識(shí)符 -------------fds[clientsock].fd=clientsock;fds[clientsock].events=POLLIN; fds[clientsock].revents=0; if (maxfd < clientsock) maxfd = clientsock;printf("maxfd=%d\n",maxfd);continue;}else {// 客戶端有數(shù)據(jù)過(guò)來(lái)或客戶端的socket連接被斷開(kāi)。char buffer[1024];memset(buffer,0,sizeof(buffer));// 讀取客戶端的數(shù)據(jù)。ssize_t isize=read(eventfd,buffer,sizeof(buffer));// 發(fā)生了錯(cuò)誤或socket被對(duì)方關(guān)閉。if (isize <=0){printf("client(eventfd=%d) disconnected.\n",eventfd);close(eventfd); // 關(guān)閉客戶端的socket。fds[eventfd].fd=-1;// 重新計(jì)算maxfd的值,注意,只有當(dāng)eventfd==maxfd時(shí)才需要計(jì)算。if (eventfd == maxfd){for (int ii=maxfd;ii>0;ii--){if ( fds[ii].fd != -1){maxfd = ii; break;}}printf("maxfd=%d\n",maxfd);}continue;}printf("recv(eventfd=%d,size=%d):%s\n",eventfd,isize,buffer);// 把收到的報(bào)文發(fā)回給客戶端。write(eventfd,buffer,strlen(buffer));}}}return 0; }epoll
相關(guān)數(shù)據(jù)結(jié)構(gòu)
typedef union epoll_data {void *ptr;int fd; // 目前唯一關(guān)注的成員uint32_t u32;uint64_t u64; } epoll_data_t;struct epoll_event {uint32_t events; /* Epoll events */epoll_data_t data; /* User data variable */ };events的取值
EPOLLIN: 關(guān)聯(lián)的文件已經(jīng)對(duì)read操作可用。(常用)EPOLLPUT: 關(guān)聯(lián)的文件已經(jīng)對(duì)write操作可用。(常用)EPOLLRDHUP: 對(duì)應(yīng)的socket連接的已經(jīng)關(guān)閉。(常用)EPOLLPRI: 緊急消息對(duì)read操作可用。(~)EPOLLERR: 對(duì)應(yīng)的文件發(fā)生錯(cuò)誤,默認(rèn)等待,不需要再手動(dòng)設(shè)置。(-)EPOLLHUP: 對(duì)應(yīng)的文件掛起,默認(rèn)等待,不需要再手動(dòng)設(shè)置。(-)EPOLLET: 設(shè)置為模式邊緣觸發(fā)模式。(默認(rèn)是水平觸發(fā)模式)EPOLLONESHOT: “Sets the one-shot behavior for the associated file descriptor.”代碼結(jié)構(gòu)
// 1、需要1個(gè)int類型接收epoll_create函數(shù)的返回值(標(biāo)識(shí)符,表示eopll實(shí)例),epoll_create的參數(shù)大于0即可,沒(méi)什么意義 int epollfd = epoll_create(1);// 2、創(chuàng)建一個(gè)epoll_event結(jié)構(gòu)并初始化,設(shè)定標(biāo)識(shí)符和監(jiān)聽(tīng)的事件 struct epoll_event ev; ev.data.fd = listensock; ev.events = EPOLLIN;// 3、使用epoll_ctl函數(shù)進(jìn)行設(shè)置 epoll_ctl(epollfd, EPOLL_CTL_ADD, listensock, &ev);// 4、while。添加刪除socket標(biāo)識(shí)符的方式與2、3流程一致 while (1) {// MAXEVENTS的值由實(shí)際需求決定struct epoll_event events[MAXEVENTS]; // -------------------------------int infds = epoll_wait(epollfd, events, MAXEVENTS, -1);if (infds < 0)...if (infds == 0)... // 查找的范圍由epoll_wait的返回值決定for (int i=0; i<infds; i++){if ((events[i].data.fd == listensock) &&(events[i].events & EPOLLIN)){...響應(yīng)客戶端連接...int clientsock = accept(li...;...// ------------------------------- // 復(fù)用全局變量ev,設(shè)置好參數(shù)后用epoll_ctl添加// 把新的客戶端添加到epoll中。memset(&ev,0,sizeof(struct epoll_event));ev.data.fd = clientsock;ev.events = EPOLLIN;epoll_ctl(epollfd, EPOLL_CTL_ADD, clientsock, &ev);}else if (events[i].events & EPOLLIN){...與客戶端通信...if (isize <=0) // 發(fā)生錯(cuò)誤或連接被斷開(kāi){// 把已斷開(kāi)的客戶端從epoll中刪除。 // ------------------------------- // 執(zhí)行流程與上文的添加基本一致,就只是修改了epoll_ctl的1個(gè)參數(shù)而已memset(&ev, 0, sizeof(struct epoll_event));ev.data.fd = events[i].data.fd;ev.events = EPOLLIN;epoll_ctl(epollfd, EPOLL_CTL_DEL, events[i].data.fd, &ev);close(events[i].data.fd);continue;}}} }// 5、關(guān)閉epoll close(epollfd);水平觸發(fā)和邊緣觸發(fā)
epoll默認(rèn)使用水平觸發(fā)。
水平觸發(fā):報(bào)告了fd后事件沒(méi)被處理或數(shù)據(jù)沒(méi)有被全部讀取,epoll會(huì)立即再報(bào)告該fd。
邊緣觸發(fā):報(bào)告了fd后事件沒(méi)被處理或數(shù)據(jù)沒(méi)有被全部讀取,epoll會(huì)下次再報(bào)告該fd。
----------------------------
pthread_equal
int pthread_equal(pthread_t tid1, pthread_t tid2);返回值:相等——非0值,不相等——0。pthread_t類型是采用數(shù)據(jù)類型來(lái)實(shí)現(xiàn)的,所以不能作為整數(shù)處理(==),得用pthread_equal來(lái)進(jìn)行比較。
一個(gè)打印pthread_t類型變量的方法
// “%lu” 和 “%lx” pthread_t tid = pthread_self(); printf("tid: %lu(0x%lx) \n", (unsigned long)tid, (unsigned long)tid);// 輸出 tid: 140048119990016(0x7f5f7e718700)pthread_self
pthread_t pthread_self(void);返回值:調(diào)用函數(shù)的線程ID通常與pthread_equal一起使用。
pthread_create
int pthread_create(pthread_t* restrict tidp, constpthread_attr_t* restrict attr, void* (*start_rtn)(void*), void* restrict arg);返回值:成功——0;失敗——錯(cuò)誤編號(hào)。restrict:C語(yǔ)言中的一種類型限定符(Type Qualifiers),用于告訴編譯器,對(duì)象已經(jīng)被指針?biāo)?#xff0c;不能通過(guò)除該指針外所有其他直接或間接的方式修改該對(duì)象的內(nèi)容。
創(chuàng)建后的線程ID存入tidp所指的地址;
attr:屬性;
start_rtn:函數(shù)指針
arg:函數(shù)參數(shù)
線程能按順序創(chuàng)建,但不一定會(huì)按順序執(zhí)行。
函數(shù)調(diào)用失敗時(shí)會(huì)返回錯(cuò)誤碼,不會(huì)設(shè)置errno。
pthread_exit
void pthread_exit(void* rval_ptr);在不終止整個(gè)進(jìn)程的情況下,停止線程。
rval_ptr指針指向線程要返回的數(shù)據(jù)。其它線程可以通過(guò)pthread_join訪問(wèn)到這個(gè)指針。(如果要返回的數(shù)據(jù)大小<=sizeof(void*),則可以通過(guò)強(qiáng)制轉(zhuǎn)換直接返回?cái)?shù)據(jù)。)
**注意:**如返回的是個(gè)指針,必須確保在線程結(jié)束后,指針?biāo)竷?nèi)存數(shù)據(jù)仍有效。如果返回的是值,則不需要擔(dān)心。
void* func_one(void* ptr) {int val = 10;pthread_exit((void*)(long)val);// pthread_exit((void*)(long)20);// 兩種方法都能有效將返回值傳遞出去 }SIGSEGV
如果上方代碼是:
void* func_one(void* ptr) {int* val = 0;*val = 30;pthread_exit((void*)(long)val); }在gdb調(diào)試中就會(huì)有如下警告:
Program terminated with signal SIGSEGV, Segmentation fault.SIGSEGV是當(dāng)一個(gè)進(jìn)程執(zhí)行了一個(gè)無(wú)效的內(nèi)存引用,或發(fā)生段錯(cuò)誤時(shí)發(fā)送給它的信號(hào)。
與return的區(qū)別
return只是退出了函數(shù),線程仍有可能存在;pthread_exit讓線程停止。
二者都會(huì)調(diào)用清理函數(shù),其它的區(qū)別還看不懂。
pthread_join
int pthread_join(pthread_t thread, void** rval_ptr);返回值:成功——0;失敗——錯(cuò)誤編號(hào)。如果線程被取消(pthread_cancel),rval_ptr所指向的內(nèi)存單元被設(shè)置為PTHREAD_CANCELED(好像值是-1)。
如果不關(guān)心線程返回值,rval_ptr可以直接填NULL。
獲取返回值:
幫助理解:pthread_join返回后,rval_ptr所指向的內(nèi)存單元保存的就是pthread_exit的返回值(可能是數(shù)據(jù)本身,也可能是指針)。
// 1、返回?cái)?shù)據(jù)本身 void* func_one(void* ptr) {g_val = 10;pthread_exit((void*)(long)g_val); } int main() {pthread_t thread_one;pthread_create(&thread_one, NULL, func_one, NULL);int i;// &i所指向的內(nèi)存單元就是i的值,也就是g_val的值pthread_join(thread_one, (void**)&i);printf("%d\n", i); }// 2、返回的是指針 void* func_one(void* ptr) {g_val = 10;pthread_exit((void*)&g_val); } int main() {pthread_t thread_one;pthread_create(&thread_one, NULL, func_one, NULL);int *i; // &i所指向的內(nèi)存單元是指針i,仍是一個(gè)指針,也就是&g_val,解一次引用后得到數(shù)據(jù)pthread_join(thread_one, (void**)&i);printf("%d\n", *i); }pthread_cancel
int pthread_cancel(pthread_t tid);返回值:成功——0;失敗——錯(cuò)誤編號(hào)。終止同一進(jìn)程中的其它線程。
調(diào)用這一函數(shù)只是提出請(qǐng)求,并不會(huì)阻塞等待。
線程安排它退出時(shí)需要調(diào)用的函數(shù)。這樣的函數(shù)被稱為線程清理處理程序,可以設(shè)置多個(gè),存入棧中。
例子:讓一個(gè)子線程取消另一個(gè)子線程,參數(shù)的傳遞好像有點(diǎn)麻煩
int main() {...// 將tid1的地址轉(zhuǎn)為void*后傳給線程函數(shù)th_cancelpthread_create(&tid2, NULL, th_cancel, (void*)&tid1);... }void* th_cancel(void* arg) { // 先將arg轉(zhuǎn)為pthread_t*類型的指針,然后對(duì)其解引用*(...)pthread_cancel(*((pthread_t*)arg)); }pthread_cleanup_push/pop
設(shè)置線程清理處理程序。
線程結(jié)束時(shí)要執(zhí)行一些善后工作,這些代碼不方便寫在主函數(shù)中,所以有了這對(duì)函數(shù)。
void pthread_cleanup_push(void (*rtn)(void*), void *arg);void pthread_cleanup_pop(int execute);-
二者是以宏的形式定義的,pthread_cleanup_push帶有一個(gè)"{",而pthread_cleanup_pop帶有一個(gè)"}",所以必須成對(duì)使用!
-
如果使用pthread_cleanup_pop(0),則只會(huì)將棧中的一個(gè)清理程序彈出,不執(zhí)行。傳入任意非0的值都會(huì)彈出并執(zhí)行函數(shù)。
清理函數(shù)的執(zhí)行時(shí)機(jī)
**!!**清理函數(shù)執(zhí)行時(shí),執(zhí)行多少個(gè)清理函數(shù),按什么順序執(zhí)行,都取決于此時(shí)棧中的情況!!
**1、**函數(shù)運(yùn)行到pthread_cleanup_pop(非0);
**2、**線程退出。自行退出或被cancel都能觸發(fā)清理函數(shù),且會(huì)執(zhí)行棧中的所有清理函數(shù)。
注意!:如果主線程-進(jìn)程終止而導(dǎo)致的子線程終止,可能會(huì)使清理函數(shù)來(lái)不及執(zhí)行。
所以主線程總是要給子線程的運(yùn)行留下足夠的時(shí)間。
再補(bǔ)充:清理函數(shù)要被執(zhí)行,一個(gè)大前提是“棧中存有清理函數(shù)”,即線程退出時(shí),pthread_cleanup_pop被執(zhí)行的次數(shù)必須少于pthread_cleanup_push。
誤區(qū)糾正
誤區(qū)1:pthread_cleanup_pop僅僅是用來(lái)設(shè)置棧中函數(shù)在彈出時(shí)是否執(zhí)行。
糾正:pthread_cleanup_pop既是函數(shù)調(diào)用,也會(huì)在線程被結(jié)束時(shí)彈出并調(diào)用清理函數(shù)。
證:
void* func_one(void* ptr) {pthread_cleanup_push(exit_foo, (void*)(long)1);pthread_cleanup_push(exit_foo, (void*)(long)2);// pthread_exit((void*)(long)5);// 主線程用此函數(shù)創(chuàng)建了子線程后,立即調(diào)用pthread_cancelsleep(5);printf("before pop \n");pthread_cleanup_pop(1);pthread_cleanup_pop(1);printf("after pop, sleep \n");sleep(5);printf("thread exit \n");pthread_exit((void*)(long)10); }輸出結(jié)果:和預(yù)料的一樣,子線程在退出時(shí)按棧順序調(diào)用了清理函數(shù) [root@localhost coding]# make run g++ -g -o test test.cpp -lpthread ./test thread cleanup 2 thread cleanup 1 -1------------------------------------------------------void* func_one(void* ptr) {pthread_cleanup_push(exit_foo, (void*)(long)1);pthread_cleanup_push(exit_foo, (void*)(long)2);// pthread_exit((void*)(long)5);// 主線程創(chuàng)建子線程后,等待 // sleep(5);printf("before pop \n");pthread_cleanup_pop(1);pthread_cleanup_pop(1);printf("after pop, sleep \n");sleep(5);printf("thread exit \n");pthread_exit((void*)(long)10); }輸出結(jié)果:就像調(diào)用了函數(shù)一樣,在線程結(jié)束前就執(zhí)行了清理函數(shù),等到線程退出時(shí)不再執(zhí)行清理函數(shù) [root@localhost coding]# make run ./test before pop thread cleanup 2 thread cleanup 1 after pop, sleep // (約5s后) thread exit 10!收獲
被pthread_cleanup_push和pthread_cleanup_pop“包裹”起來(lái)的代碼段,就是這對(duì)push/pop要負(fù)責(zé)善后的代碼段。
實(shí)際應(yīng)用中應(yīng)該是像這樣吧?:
pthread_cleanup_push(文件清理函數(shù)); ...文件操作... pthread_cleanup_pop(1);pthread_cleanup_push(IO清理函數(shù)); ...IO操作... pthread_cleanup_pop(1);pop的位置
例子:在本例中,return和pthread_exit效果一樣。
void* func_one(void* ptr) {pthread_cleanup_push(exit_foo, (void*)(long)1);pthread_cleanup_push(exit_foo, (void*)(long)2);// 一、 // pthread_exit((void*)(long)5);pthread_cleanup_pop(0);pthread_cleanup_pop(1);// 二、pthread_exit((void*)(long)10); }// ------------------------------- // 一、 [root@localhost coding]# make run ./test thread cleanup 2 thread cleanup 1 5 // 二、 [root@localhost coding]# make run g++ -g -o test test.cpp -lpthread ./test thread cleanup 1 10將pthread_exit放在pthread_cleanup_pop之前,所有的清理函數(shù)都會(huì)執(zhí)行,無(wú)論是否傳入?yún)?shù)0。(是否可以理解為“還沒(méi)有讀到pthread_cleanup_pop的具體設(shè)置,線程就退出了,所以默認(rèn)都彈出并執(zhí)行”)
**pthread_cancel同理!**如果一個(gè)子線程在執(zhí)行到pthread_cleanup_pop之前就被其它線程cancel,也會(huì)執(zhí)行所有的清理函數(shù),而不管具體的pop設(shè)置。
pthread_detach
可以讓線程函數(shù)自己調(diào)用pthread_detach(pthread_self()),或是由別的線程調(diào)用pthread_detach(tid)。
int pthread_detach(pthread_t tid);返回值:成功——0;失敗——錯(cuò)誤編號(hào)。讓ID為tid的線程處于分離狀態(tài),不能再用pthread_join對(duì)其進(jìn)行等待。
對(duì)分離狀態(tài)的線程調(diào)用pthread_join不會(huì)阻塞調(diào)用函數(shù)的線程,且pthread_join接收的數(shù)據(jù)沒(méi)有意義。
目前只知道會(huì)對(duì)pthread_join有影響。cancel和push/pop無(wú)影響。
----------------------------
除了對(duì)應(yīng)的init函數(shù),似乎所有鎖都能用一個(gè)PTHREAD_XXX_INITIALIZER變量進(jìn)行賦值來(lái)完成初始化。
pthread_mutex_init/destroy
PTHREAD_MUTEX_INITIALIZER可對(duì)靜態(tài)分配的互斥鎖(或作為全局變量的互斥鎖)進(jìn)行初始化。
// 2個(gè)參數(shù) int pthread_mutex_init(pthread_mutex_t* restrict mutex, const pthread_mutexattr_t* restrict attr);int pthread_mutex_destroy(pthread_mutex_t* restrict mutex);2個(gè)函數(shù)的返回值:成功——0;失敗——錯(cuò)誤編號(hào)。將attr設(shè)為NULL,可使用默認(rèn)的初始化互斥量。
pthread_mutex_lock/trylock/unlock
int pthread_mutex_lock(pthread_mutex_t* mutex);int pthread_mutex_trylock(pthread_mutex_t* mutex);int pthread_mutex_unlock(pthread_mutex_t* mutex);3個(gè)函數(shù)的返回值:成功——0;失敗——錯(cuò)誤編號(hào)。使用pthread_mutex_lock對(duì)互斥量上鎖,如果互斥量已經(jīng)上鎖,調(diào)用pthread_mutex_lock的函數(shù)會(huì)被阻塞,直到互斥量被解鎖。
pthread_mutex_unlock解鎖互斥量。
如果不希望線程被阻塞,可以使用pthread_mutex_trylock嘗試對(duì)互斥量加鎖。如果互斥量可用,加鎖;否則返回EBUSY。
pthread_mutex_timedlock
tsptr使用的是絕對(duì)時(shí)間:1970年1月1日以來(lái)經(jīng)過(guò)的秒數(shù)。
int pthread_mutex_timedlock(pthread_mutex_t* restrict mutex, const struct timespec* restrict tsptr);返回值:成功——0;失敗——錯(cuò)誤編號(hào)。功能與pthread_mutex_lock基本等價(jià),但到達(dá)超時(shí)時(shí)間時(shí),會(huì)返回錯(cuò)誤碼ETIMEDOUT。即這個(gè)函數(shù)愿意阻塞等待X秒。
tsptr:timespec結(jié)構(gòu),2個(gè)成員:time_t tv_sec、long tv_nsec分別表示s和ns。
1*109ns= 1s 。
獲取時(shí)間參數(shù)的例子
struct timespec tout; clock_gettime(CLOCK_REALTIME, &tout); tout.tv_sec += 10; pthread_mutex_timedlock(&lock, &tout);pthread_rwlock_init/destroy
PTHREAD_RWLOCK_INITIALIZER可對(duì)靜態(tài)分配的讀寫鎖(或作為全局變量的讀寫鎖)進(jìn)行初始化。
// 2個(gè)參數(shù) int pthread_rwlock_init(pthread_rwlock_t* restrict rwlock, const pthread_rwlockattr_t* restrict attr);int pthread_rwlock_destroy(pthread_rwlock_t* rwlock);2個(gè)函數(shù)的返回值:成功——0;失敗——錯(cuò)誤編號(hào)。只有當(dāng)讀狀態(tài)的鎖的使用頻率遠(yuǎn)高于寫狀態(tài)的鎖的使用頻率,使用讀寫鎖才可以改善性能。
pthread_rwlock_rdlock/wrlock/unlock
int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);int pthread_rwlock_unlock(pthread_rwlock_t* rwlock);3個(gè)函數(shù)的返回值:成功——0;失敗——錯(cuò)誤編號(hào)。pthread_rwlock_rdlock:在讀模式下鎖定讀寫鎖。
pthread_rwlock_wrlock:在寫模式下鎖定讀寫鎖。
pthread_rwlock_unlock:2種模式的讀寫鎖都能解鎖。
pthread_rwlock_tryrdlock/trywrlock
int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock);int pthread_rwlock_trywrlock(pthread_rwlock_t* rwlock);返回值:成功——0;失敗——錯(cuò)誤編號(hào)。可以獲得鎖,加鎖,返回0;否則返回EBUSY。
pthread_rwlock_timedrdlock/timedwrlock
int pthread_rwlock_timedrdlock(pthread_rwlock_t* restrict rwlock, const struct timespec* restrict tsptr);int pthread_rwlock_timedwrlock(pthread_rwlock_t* restrict rwlock, const struct timespec* restrict tsptr);返回值:成功——0;失敗——錯(cuò)誤編號(hào)。超時(shí)到期都返回ETIMEDOUT。都使用絕對(duì)時(shí)間。
總結(jié)
以上是生活随笔為你收集整理的【笔记整理 - 多线程编程】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 零基础Ar学习之Unity3D运行Eas
- 下一篇: 杰力科创--单片机--DLT8P65SA