多個線程共享資源時一定會存在沖突,試想,假設多個線程都要往一段內存中寫數據,按照預期應該是一個現程寫完數據后,內存地址的偏移增加,另一個線程在緊接着這個偏移地址往下寫,每個線程寫入的數據都是完整的,但實際上,由於線程是並發的,可能一個線程把自己的數據還沒有寫完,另外的線程已經開始寫了,結果整個內存區就亂成一團了。因此,多線程運行的情況下,線程同步是必需的,也就是說對於共享資源,尤其是“寫”資源,當某某個線程正在使用時,必須被獨占,其他線程只能等待該線程使用完畢並釋放掉之后,才可以使用。同步的基礎手段是采用互斥鎖和條件變量。
1、線程同步的概念
當多個控制線程共享相同的內存時,需要確保每個線程看到一致的數據視圖。如果每個線程使用的變量都是其他線程不會讀取或修改的,那么就不會存在一致性問題。同樣地,如果變量是只讀的,多個線程同時讀取該變量也不會有一致性問題。但是,當某個線程可以修改變量,而其他線程也可以讀取或修改這個變量的時候,就需要對這些線程進行同步,以確保它們在訪問變量的存儲內容時不會訪問到無效的數值。
當一個線程修改變量時,其他線程在讀取這個變量的值時就可能會看到不一致的數據。在變量修改時間多於一個存儲器訪問周期的處理器結構中,當存儲器讀與存儲器寫這兩個周期交叉時,這種潛在的不一致性就會出現。當然,這種行為是與處理器結構相關的,但是可移植性程序並不能對使用何種處理器結果作出假設。
圖11-2描述了兩個線程讀寫相同變量的假設例子。在這個例子中,線程A讀取變量然后給這個變量賦予一個新的值,但寫操作需要兩個存儲器周期。當線程B在這兩個存儲器寫周期中間讀取這個相同的變量時,它就會得到不一致的值。
為了解決這個問題,線程不得不使用鎖,在同一時間只允許一個線程訪問該變量。圖11-3描述了這種同步。如果線程B希望讀取變量,它首先要獲取鎖;同樣地,當線程A更新變量時,也需要獲取這把同樣的鎖。因而線程B在線程A釋放鎖以前不能讀取變量。
當兩個或多個線程試圖在同一時間修改同一變量時,也需要進行同步。考慮變量遞增操作的情況(圖11-4),增量操作通常可分為三步:
(1)從內存單元讀入寄存器。
(2)在寄存器中進行變量值的增加。
(3)把新的值寫回內存單元。
兩個非同步的線程對同一個變量做增量操作
如果兩個線程試圖在幾乎同一時間對同一變量做增量操作而不進行同步的話,結果就可能出現不一致。變量可能比原來增加了1,也有可能比原來增加了2,具體是1還是2取決於第二個線程開始操作時獲取的數值。如果第二個線程執行第一步要比第一個線程執行第三步早,第二個線程讀到的初始值就與第一個線程一樣,它為變量加1,然后再寫回去,事實上沒有實際的效果,總的來說變量只增加了1。
如果修改操作是原子操作,那么就不存在競爭。在前面的例子中,如果增加1只需要一個存儲器周期,那么就沒有競爭存在。如果數據總是以順序一致的方式出現,就不需要額外的同步。當多個線程並不能觀察到數據的不一致時,那么操作就是順序一致的。在現代計算機系統中,存儲訪問需要多個總線周期,多處理器的總線周期通常在多個處理器上是交叉的,所以無法保證數據是順序一致的。
在順序一致的環境中,可以把數據修改操作解釋為運行線程的順序操作步驟。可以把這樣的情形描述為“線程A對變量增加了1,然后線程B對變量增加了1,所以變量的值比原來的大2”,或者描述為“線程B對變量增加了1,然后線程A對變量增加了1,所以變量的值比原來的大2”。這兩個線程的任何操作順序都不可能讓變量出現除了上述值以外的其他數值。
除了計算機體系結構的因素以外,程序使用變量的方式也會引起競爭,也會導致不一致的情況發生。例如,可能會對某個變量加1,然后基於這個數值作出某種決定。增量操作這一步和作出決定這一步兩者的組合並非原子操作,因而給不一致情況的出現提供了可能。
2、互斥鎖
可以通過使用pthread的互斥接口保護數據,確保同一時間只有一個線程訪問數據。互斥量(mutex)從本質上說是一把鎖,在訪問共享資源前對互斥量進行加鎖,在訪問完成后釋放互斥量上的鎖。對互斥量進行加鎖以后,任何其他試圖再次對互斥量加鎖的線程將會被阻塞直到當前線程釋放該互斥鎖。如果釋放互斥鎖時有多個線程阻塞,所有在該互斥鎖上的阻塞線程都會變成可運行狀態,第一個變為運行狀態的線程可以對互斥量加鎖,其他線程將會看到互斥鎖依然被鎖住,只能回去再次等待它重新變為可用。在這種方式下,每次只有一個線程可以向前執行。
在設計時需要規定所有的線程必須遵守相同的數據訪問規則,只有這樣,互斥機制才能正常工作。操作系統並不會做數據訪問的串行化。如果允許其中的的某個線程在沒有得到鎖的情況下也可以訪問共享資源,那么即使其他的線程在使用共享資源前都獲取了鎖,也還是會出現數據不一致的問題。
互斥變量用pthread_mutex_t數據類型表示,在使用互斥變量以前,必須首先對它進行初始化,可以把它置為常量PTHREAD_MUTEX_INITIALIZER(只對靜態分配的互斥量),也可以通過調用pthread_mutex_init函數進行初始化。如果動態地分配互斥量(例如通過調用malloc函數),那么在釋放內存前需要調用pthread_mutex_destroy。
#include <pthread.h> int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); int pthread_mutex_destroy(pthread_mutex_t *mutex); 返回值:若成功則返回0,否則返回錯誤編號
要用默認的屬性初始化互斥量,只需把attr設置為NULL。
對互斥量進行加鎖,需要調用pthread_mutex_lock,如果互斥量已經上鎖,調用線程將阻塞直到互斥量被解鎖。對互斥量解鎖,需要調用pthread_mutex_unlock。其函數原型如下:
#include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); 返回值:若成功則返回0,否則返回錯誤編號
如果現場不希望被阻塞,他可以使用pthread_mutex_trylock()嘗試對互斥量進行加鎖。如果調用函數pthread_mutex_trylock()時,互斥量處於未鎖住狀態,那么函數pthread_mutex_trylock()就會鎖住互斥量,不會出現阻塞並返回0;否則pthread_mutex_trylock()就會失敗,不能鎖住互斥量,而返回EBUSY。
示例:使用互斥量實現線程同步。
pthread.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> int num1=0; int num2=0; pthread_mutex_t mutex; void *func(void *arg) { while(1) { num1++; num2++; if(num1!=num2) { printf("%d!=%d\n",num1,num2); } } } int main(int atgc,char *argv[]) { pthread_t tid1,tid2; int err; err=pthread_mutex_init(&mutex,NULL); if(err!=0) { perror("pthread_mutex_init()"); exit(1); } err=pthread_create(&tid1,NULL,func,NULL); if(err!=0) { perror("pthread_creat()"); exit(1); } err=pthread_create(&tid2,NULL,func,NULL); if(err!=0) { perror("pthread_creat()"); exit(1); } while(1) { sleep(3); } exit(0); }
編譯運行結果如下,按ctrl+c停止
該示例程序中,num1和num2均初始化為0,按照常理,num1遞增和num2遞增之后也應相等,但是輸出的結果卻不相等,造成上面現象的原因很多,例如num1在線程tid1中遞增后,立即切換到tid2,在線程tid2中完成num1和num2的遞增,從而造成num1與num2的不相等。
修改線程函數如下
void *func(void *arg) { pthread_mutex_lock(&mutex);//加入互斥鎖 while(1) { num1++; num2++; if(num1!=num2) { printf("%d!=%d\n",num1,num2); pthread_mutex_unlock(&mutex);//解除互斥鎖 } } }
編譯運行結果如下:
此時,程序不再有任何輸出,原因在於在每個線程中,在num1和num2遞增之前加鎖互斥量,在num1和num2遞增判斷之后解鎖互斥量,保證了在互斥量加鎖期間,num1和num2的變化相同。
3、自旋鎖
自旋鎖與互斥鎖功能一樣,唯一一點不同的就是互斥量阻塞后休眠讓出cpu,而自旋鎖阻塞后不會讓出cpu,會一直忙等待,直到得到鎖!!!
自旋鎖在用戶態使用的比較少,在內核使用的比較多!自旋鎖的使用場景:鎖的持有時間比較短,或者說小於2次上下文切換的時間。
自旋鎖在用戶態的函數接口和互斥量一樣,把pthread_mutex_xxx()中mutex換成spin,如:pthread_spin_init()。
4、讀寫鎖
上面介紹的互斥量,它只有兩個狀態,要么是加鎖狀態,要么是不加鎖。假如現在一個線程 a 只是想讀一個共享變量 i ,因為不確定是否會有線程去寫他,所以我們還是要對它進行加鎖。但是這時候又一個線程 b 試圖讀共享變量i ,於是發現被鎖住,那么b不得不等到a釋放了鎖后才能獲得鎖並讀取 i 的值,但是兩個讀取操作即使是幾乎同時發生也並不會像寫操作那樣造成競爭,因為他們不修改變量的值。所以我們期望如果是 多個線程試圖讀取共享變量的 值的話,那么他們應該可以立刻獲取而不需要等待前一個線程釋放因為讀而加的鎖。讀寫鎖可以很好的解決上面的問題。他提供了比互斥量跟好的並行性。因為以讀模式加鎖后當又有多個線程僅僅是試圖再以讀模式加鎖然時,並不會造成這些線程阻塞在等待鎖的釋放上。
讀寫鎖是互斥鎖的一種改進,對於讀操作遠多於寫操作的程序,使用讀寫鎖更能提高效率。其實際是一種特殊的自旋鎖,它把對共享資源的訪問者划分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。
讀寫鎖的特點是:
(1)當讀寫鎖是寫加鎖時,在這個鎖被解鎖之前,所有試圖對這個鎖加鎖的線程都會被阻塞。
(2)當讀寫鎖是讀加鎖時,在這個鎖被解鎖之前,所有試圖以讀模式對他進行加鎖的線程都可以得到訪問權,但是如果線程以寫模式對此鎖加鎖時會造成阻塞,直到所有線程釋放讀鎖
讀寫鎖需要使用動態初始化,使用完成后需要銷毀,初始化和銷毀函數原型如下:
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr); int pthread_rwlockattr_destory(pthread_rwlockattr_t *attr);
兩個函數的用法和互斥鎖的初始化銷毀相同。
讀加鎖和寫加鎖函數原型如下:
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
非阻塞方式的讀加鎖和寫加鎖原型如下:
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
釋放鎖函數原型如下:
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
無論是讀加鎖還是寫加鎖,釋放鎖函數都是同一個。以上函數成功后返回0,其他為錯誤原因。
示例:創建4個線程,2個線程讀鎖,2個線程寫鎖,觀察4個線程進入臨界區的順序:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> /* 初始化讀寫鎖 */ pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; /* 全局資源 */ int global_num = 10; void err_exit(const char *err_msg) { printf("error:%s\n", err_msg); exit(1); } /* 讀鎖線程函數 */ void *thread_read_lock(void *arg) { char *pthr_name = (char *)arg; while (1) { /* 讀加鎖 */ pthread_rwlock_rdlock(&rwlock); printf("線程%s進入臨界區,global_num = %d\n", pthr_name, global_num); sleep(1); printf("線程%s離開臨界區...\n", pthr_name); /* 讀解鎖 */ pthread_rwlock_unlock(&rwlock); sleep(1); } return NULL; } /* 寫鎖線程函數 */ void *thread_write_lock(void *arg) { char *pthr_name = (char *)arg; while (1) { /* 寫加鎖 */ pthread_rwlock_wrlock(&rwlock); /* 寫操作 */ global_num++; printf("線程%s進入臨界區,global_num = %d\n", pthr_name, global_num); sleep(1); printf("線程%s離開臨界區...\n", pthr_name); /* 寫解鎖 */ pthread_rwlock_unlock(&rwlock); sleep(2); } return NULL; } int main(void) { pthread_t tid_read_1, tid_read_2, tid_write_1, tid_write_2; /* 創建4個線程,2個讀,2個寫 */ if (pthread_create(&tid_read_1, NULL, thread_read_lock, "read_1") != 0) err_exit("create tid_read_1"); if (pthread_create(&tid_read_2, NULL, thread_read_lock, "read_2") != 0) err_exit("create tid_read_2"); if (pthread_create(&tid_write_1, NULL, thread_write_lock, "write_1") != 0) err_exit("create tid_write_1"); if (pthread_create(&tid_write_2, NULL, thread_write_lock, "write_2") != 0) err_exit("create tid_write_2"); /* 隨便等待一個線程,防止main結束 */ if (pthread_join(tid_read_1, NULL) != 0) err_exit("pthread_join()"); return 0; }
編譯運行結果如下
可以看到,讀鎖可以一起進入臨界區,而寫鎖在臨界區里面等1秒都不會有其他線程能進來!!!
5、死鎖
互斥鎖使用不當的時候容易出現死鎖,死鎖有兩種情況,
(1)當一個線程試圖對一個互斥鎖加鎖兩次時會出現死鎖,一般出現這種情況是因為函數外邊進行了一次加鎖,而調用的函數內部又嘗試去加鎖(包括函數的遞歸調用),從而導致死鎖。因此,一般情況下,對於加解鎖中間代碼段不建議有函數,應該只是一些簡單操作。
(2)另一種情況是,當多個線程有多個互斥鎖,並且需要相互加鎖是容易發生死鎖。比如A線程加鎖了互斥鎖x,又在請求互斥鎖y,而B線程加鎖了互斥鎖y,又在請求互斥鎖x,結果就發生了死鎖。這種情況需要我們在編程的時候清楚互斥鎖的順序,從而避免出現這種情況。
6、條件變量
互斥鎖用於上鎖,條件變量用於等待。這兩種不同類型的同步都是需要的。條件變量的主要作用是等待某個條件,如果條件不滿足,則線程被投入睡眠,當條件滿足后,線程將被喚醒繼續工作。條件變量總是和互斥鎖一塊使用。
Linux系統使用pthread_cond_t類型來標識條件變量。
1、條件變量初始化
條件變量的初始化和互斥鎖類似,可以使用靜態初始化,也可以動態分配;如果使用靜態初始化,可以做如下定義:
pthread_cond_t cond=PTHREAD_COND_INTITIALIZER;
如果需要使用動態分配的,則使用以下來給你個函數進行:
int pthread_cond_init(pthread_cond_t *cv,const pthread_condattr_t *cattr); int pthread_cond_destory(pthread_cond_t *cv);
以上兩個函數成功則返回0,其他為錯誤。同樣如果只是用條件變量的默認屬性的話,初始化函數的第二個參數置位NULL即可。以上兩個函數成功則返回0,其他為錯誤原因。
2、條件變量等待
條件變量的作用就是等待某個條件。等待函數有兩個,一個是定時等待,一個是無限等待。這兩個函數原型如下:
int pthread_cond_wait(pthread_cond_t *cv,pthread_mutex_t *mutex); int pthread_cond_timedwait(pthread_cond_t *cv,pthread_mutex_t *mp,const struct timespec *abstime);
以上兩個函數成功則返回,其他為錯誤原因。
一個線程調用函數pthread_cond_wait()在一個條件變量上發生阻塞等待,需要進行以下三個步驟:
(1)釋放互斥量。
(2)阻塞等待。
(3)當被喚醒時,重新獲得互斥量並返回。
需要注意到的是,等待時間abstime是一個絕對是件,指得是1970年1月1日0時起過去的秒數和納秒數。通常的用法,比如說等待3分鍾,先獲取當前時間的總秒數,然后再加上3分鍾的秒數。即180秒。abtime的定義如下:
struct timespec { time_t tv_sec; long tv_nsec; }
當等待時間到達時,條件還沒有滿足,即pthread_cond_timedwait函數返回ETIMEDOUT錯誤。
3、條件變量通知
當某個線程在等待條件時進入睡眠,就需要條件滿足后通知喚醒睡眠的線程去執行。通知函數有兩個:一個是通知單個的線程,一個是廣播通知。一般情況下,除非特別明確哪一個線程在等待條件變量時使用單線程通知函數,否則全部使用廣播通知函數。以下為兩個函數原型:
int pthread_cond_signal(pthread_cond_t *cv); int pthread_cond_broadcast(pthread_cond_t *cv);
以上兩個函數成功返回0,其他為錯誤原因。
示例:
使用條件變量實現線程的同步。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> struct msg { struct msg *next; int num; }; struct msg *head; pthread_cond_t has_product=PTHREAD_COND_INITIALIZER; pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER; void *consumer(void *p) { struct msg *mp; while(1) { pthread_mutex_lock(&lock); while(head==NULL) pthread_cond_wait(&has_product,&lock); mp=head; head=mp->next; pthread_mutex_unlock(&lock); printf("Consume%d\n",mp->num); free(mp); sleep(rand()%5); } } void *producer(void *p) { struct msg *mp; while(1) { mp=malloc(sizeof(struct msg)); mp->num=rand()%1000+1; printf("Prpduce%d\n",mp->num); pthread_mutex_lock(&lock); mp->next=head; head=mp; pthread_mutex_unlock(&lock); pthread_cond_signal(&has_product); sleep(rand()%5); } } int main(int argc,char *argv[]) { pthread_t tid1,tid2; srand(time(NULL));//隨機數發生器 pthread_create(&tid1,NULL,producer,NULL); pthread_create(&tid2,NULL,consumer,NULL); pthread_join(tid1,NULL); pthread_join(tid2,NULL); exit(1); }
編譯運行結果如下
7、信號量參考進程通信。
8、消息隊列
POSIX消息隊列是給定進程中各線程的一種通信方式.POSIX指得是可移植性操作系統接口(Portable Operating System Interface),其系列函數如下:
1、mq_open
mq_open函數用來創建或者打開一個消息隊列,該函數的原型如下:
mqd_t mq_open(const char* name,int oflag,.../*mode_t mode,struct mq_attr* attr */
該函數處理兩個必需的參數外還有兩個變參,各參數含義如下。
name:用於創建或打開消息隊列的名字。POSIX要求該名字必須以”/“開頭,並且后邊不允許再出現”/“符號,比如”/MsgQueue".在早期UNIX中,如果遵守這樣的規定,系統可能會在根目錄下去建立消息隊列的文件,在沒有root權限的情況下,函數的調用將會失敗。但現在眾多的UNIX\Linux都支持POSIX標准了,也就是說,系統並不會在根目錄下建立消息隊列文件,可能會在/tmp等目錄下建立,也有可能根本就不在磁盤上差個創建文件。
o_flag:消息隊列打開模式,與open類似。
后面的兩個參數則是需要新創建消息隊列時必須指定的兩個參數:一個是權限,和文件的權限類似,一個是消息隊列屬性。
2、mq_close
mq_close函數是關閉一個消息隊列。值得注意的是,如同關閉一個文件一樣,僅僅是關閉了描述符,之前曾經打開過的文件描述符不再可用,但是物理文件還是存在的,關閉消息隊列也是一樣的,僅僅是關閉描述符。但實際的消息隊列還是存在的,如果要徹底刪除則需要使用mq_unlink。mq_close原型如下:
int mq_close(mqd_t mqdes); 該函數成功返回0,失敗返回-1.
3、mq_unlink
mq_unlink函數是從物理上刪除一個消息隊列。函數原型如下:
int mq_unlink(const char* name);
該函數成功返回0,失敗返回-1.
4、mq_getarrt和mq_setarrt
這兩個函數分別是用來獲取消息隊列的屬性和設置消息隊列的屬性,函數原型如下:
int mq_getattr(mqd_t mqdes,struct mq_attr* attr); int mq_setattr(mqd_t mqdes,const struct mq_attr* attr, struct mq_attr* oattr);
函數成功返回0,失敗返回-1.
在mq_open函數中已經介紹了消息隊列的屬性。對於mq_setattr函數來講,這些屬性中唯一可用的就是設置消息隊列為非阻塞模式。該函數中第二個參數是需要設置的屬性,第三個參數是返回原來的屬性。
5、mq_send和mq_receive
mq_send和mq_receive分別是發送消息和接收消息函數,兩個函數的原型如下:
int mq_send(mqd_t mqdes,char* ptr,size_t len,unsigned int prio); ssize_t mq_receive(mqd_t mqdes,char* ptr ,size_t len,unsigned int* prio);
函數成功返回0,失敗返回-1.
這兩個函數的參數基本差不多。第一個參數是消息隊列描述符,第二個參數是發送(接收)消息的緩沖區,第三個參數是發送(接收)消息的長度,第四個參數是優先級。
在mq_receive 函數中,參數len的長度必須大於等於該消息隊列的單個消息最大長度,即消息隊列屬性中msgsize,否則將會立即返回EMSGSIZE錯誤。優先級制定為0,即發送函數不使用優先級。接收函數中,如果優先級參數非空,則將當前接收到的消息優先級填入。
6、mq_notify
mq_notify就是為消息隊列產生一個異步的事件通知。當程序調用mq_receive函數從一個消息隊列接收消息時,有兩種情況:一種是消息隊列為阻塞模式;一種是非阻塞模式。當消息隊列處於阻塞模式的時候,調用mq_receive函數接收消息。如果沒有消息的話,則會阻塞在mq_receive調用。當消息隊列處於非阻塞模式的時候,如果沒有消息,則會立即返回。如果一個單進程、單線程的應用,只從一個消息隊列接受消息,那么使用阻塞方式,程序工作就會很正常。但是,如果需要從多個消息隊列中接收消息,顯然阻塞方式下根本沒有辦法處理。而非阻塞方式可以處理多個消息隊列,但需要使用輪詢的方式去探測,很顯然這是對於CPU一種極大的浪費。於是在這種情況下,我們使用一種異步的通知機制來完成從消息隊列接收消息的處理,而mq_notify函數的作用就是發出通知。mq_notify函數原型如下:
int mq_notify(mqd_t mqdes,const sruct sigevent* notification(; 成功返回0,否則返回-1
參數mqdes:消息隊列描述符。
參數notification:產生通知的相關行為。
struct sigevent定義如下:
union signal { int sival_int; void* sival_ptr; } struct sigevaent { int sigev_notify; int sigval sigev_value; void(*sigev_notify_function)(union sigval); pthread_attr_t* sigev_notify_attributes; }
sigev_notify表示通知的處理方式,取值為SIGEV_NONE,SIGEV_SIGANAL,SIGEV_SIGNAL,SIGEV_THREAD.SIGEV_NONE表示什么都不做,SIGEV_SIGNAL表示產生一個信號,SIGEV_THREAD表示啟動一個線程處理。
sigev_signo表示要產生的信號值(信號編號,比如SIGUSR1).
sigev_value表示需要傳遞個新啟動的線程的參數。
sigev_notify_function表示線程入口函數。
sigev_notify_attributes表示新啟動的線程的屬性。
需要注意的是,部分操作系統可能並不支持取值為SIGEV_THREAD的形式。當取值為SIGEV_SIGNAL時,后邊的三個字段都不需要填寫,只需要填寫sigev_signo即可。
在使用mq_notify函數時,如果參數notification為空指針,並且當前進程已經注冊到該消息隊列,再如此調用的話,將會取消這個注冊。如果notification為非空,則表示當前進程注冊到該消息隊列,並希望收到消息到達的通知。
另外需要特別注意的有以下幾點:
(1)任意時刻只有一個進程可以被注冊為接收某個消息隊列的通知。也就是說,不可能有2個或以上的進程同時注冊在某個消息隊列上等候通知。
(2)如果通知被發送到該注冊的進程后,改注冊立刻被取消。也就是說,如果還需要通知的話,則需要再次調用mq_notify 函數進行注冊。
(3)只有消息放置到空的消息隊列的時候,才會發出通知。也就是說,如果接收進程處理一個消息比較慢,導致消息隊列已經有積攢的消息,這個時候,新的消息再加入到消息隊列的時候,是不會產生通知的,除非阿精消息隊列中的所有消息全部收空。
(4)使用mq_reiveive以阻塞的方式調用的優先級方式比通知的優先級高。也就是說,假設有個進程以阻塞方式調用mq_receive接收消息隊列消息,另外一個進程使用注冊方式進行接收同一個消息隊列的消息,那么注冊方式的進程永遠都不會收到通知。
7、消息隊列的限制
在創建消息隊列的時候有兩個字段mq_maxmsg和mq_msgsize,分別是最大消息個數和單個消息最大長度,很顯然這兩個字段及決定了需要占用的最大虛擬內存或者磁盤空間。POSIX並沒有對這兩個值進行限制,因此在實際使用中,應該根據使用的操作系統進行設定。
另外,在消息隊列使用上,有如下其他兩個限制。
MQ_OPEN_MAX:一個進程能偶同時打開的消息隊列最大個數,POSIX要求至少為8.
MQ_PRIO_MAX:任意消息的最大優先級值加1,POSIX要求至少為32.