多程序和多線程間通信總結[一]
- 1. 程序間通信
-
- 1.1 socket - 網絡中程序間通信
-
- 1.1.1 socket
- 1.1.2 代碼示例
- 1.2 消息隊列 - 同一主機上的程序通信方式
-
- 1.2.1. Linux中的消息隊列其實就是一個檔案。
- 1.2.2. 消息隊列的本質其實是一個核心提供的連結清單。
- 1.2.3. 消息隊列的缺點
- 1.2.4 消息隊列接口:ftok msgget msgsnd msgrcv msgctl 接口
- 1.2.1 僞代碼示例:
- 1.3 信号量(Semaphore) - 同一主機上的程序通信方式
-
- 1.3.1 信号量接口 - semget semctl semop
- 1.4 信号(Signal) - 同一主機上的程序通信方式
-
- 1.4.1 常用信号
- 1.4.2 信号處理的幾個函數: Signal Sigaction kill 和 alarm
- 1.5 管道(PIPE) - 同一主機上的程序通信方式
-
- 1.5.1 匿名管道
- 1.5.2 命名管道
- 1.5.3 總結
- 2. 線程間通信
-
- 2.1 互斥量+條件變量
-
- 2.1.1 僞代碼示例:
- 2.1.2 實際代碼示例
- 2.1.3 條件變量始終與互斥鎖一起使用。
- 2.2 eventfd - 通知/等待機制
-
- 2.2.1 簡單/僞代碼實作:
- 2.2.2 其他eventfd示例及其參考我的部落格
- 2.3 信号量
-
- 2.3.1 簡單/僞代碼實作:
- 2.3.2 詳細及示例代碼參考我的部落格
- 2.4 信号
- 2.5 鎖機制:包括互斥鎖、條件變量、讀寫鎖和自旋鎖。
-
- 2.5.1 互斥鎖
- 2.5.2 讀寫鎖
- 2.5.3 條件變量
- 2.5.4 自旋鎖
1. 程序間通信
程序間通信分為兩個:
- 同一個主機上的程序間通信:消息隊列,信号量(Semaphore), 共享記憶體(Shared Memory),管道(PIPE), 有名管道(FIFO), 和信号(Signal)
- 網絡中的程序間通信;Socket
1.1 socket - 網絡中程序間通信
1.1.1 socket
說白了Socket是應用層與TCP/IP協定族通信的中間軟體抽象層,它是一組接口
主要功能是将程序發送的各種請求,映射到建立套接口時指定的,與協定有關的具體實作上
可以了解為: socket即是一種特殊的檔案,一些socket函數就是對其進行的操作(讀/寫IO、打開、關閉)
圖示例:
圖示例參考:
https://www.cnblogs.com/straight/articles/7660889.html
1.1.2 代碼示例
/* 用戶端 建立,連結,讀寫資料 */
socket(AF_INET, SOCK_STREAM, 0)
connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)
write(sockfd, cCliSendMsg, sizeof(cCliSendMsg));
read(sockfd, cSerRcvMsg, sizeof(cSerRcvMsg))
/* 服務端 建立,綁定端口,監聽,接收,讀寫資料 */
socket(AF_INET, SOCK_STREAM, 0)
bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)
listen(sockfd, 5);
accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
read(newsockfd, cCliRcvMsg, sizeof(cCliRcvMsg));
write(newsockfd, cSerSendMsg, sizeof(cSerSendMsg));
參考:
之前的文章:socket(6):代碼示例
https://blog.csdn.net/lqy971966/article/details/102570084
1.2 消息隊列 - 同一主機上的程序通信方式
1.2.1. Linux中的消息隊列其實就是一個檔案。
1.2.2. 消息隊列的本質其實是一個核心提供的連結清單。
核心基于這個連結清單,實作了一個資料結構,并且通過維護這個資料結構來維護這個消息隊列。
向消息隊列中寫資料,實際上是向這個資料結構中插入一個新結點;
從消息隊列彙總讀資料,實際上是從這個資料結構中删除一個結點。
1.2.3. 消息隊列的缺點
- 每個消息的最大長度是有上限的(MSGMAX) 其中,ubuntu下 MSGMAX = 8192
- 每個消息隊列的總的位元組數(MSGMNB) 其中,ubuntu下 MSGMNB= 16384
- 系統上消息隊列的總數上限(MSGMNI) 其中,ubuntu下 MSGMNI= 32000
- 可以用cat /proc/sys/kernel/msgmax檢視具體的資料
1.2.4 消息隊列接口:ftok msgget msgsnd msgrcv msgctl 接口
/* 建立key_t值,稱為IPC鍵值 */
key_t ftok(const char *pathname, int proj_id)
/* 建立和通路一個消息隊列 */
int msgget(key_t key, int msgflg);
/* 把一條消息添加到消息隊列中 */
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
/* 讀取消息隊列 */
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
/* 消息隊列的控制函數,如删除 */
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
1.2.1 僞代碼示例:
/* 建立消息隊列對象 iMsgId */
INT iMsgId;
key_t key;
/* pcPath 具體檔案,函數ftok把一個已存在的路徑名和一個整數辨別符轉換成一個key_t值,稱為IPC鍵值*/
key = ftok(pcPath, 0x12345678);
iMsgId = msgget(key, IPC_CREAT|0700);
/* 發送消息 */
typedef struct tagMSGBUF
{
long lMtype;
char acMtext[1]; /* 可變數組 */
}MSG_BUF_S; /* MSG_BUF_S 消息類型是 msgrcv 規定的格式 */
MSG_BUF_S *pstMsg = NULL;
CHAR *pcBuffer = NULL;
pstMsg->lMtype = 1; /* 沒用 */
memcpy(pstMsg->acMtext, pcBuf, sizeof(pstMsg->acMtext)); /* pcBuf 具體消息 */
key = ftok(pcPath, 0x12345678);
iMsgId = msgget(key, 0700); /* 取消息隊列id */
iRet = msgsnd(iMsgId, pstMsg, (size_t)(UINT)iLen, (INT)0); /* iLen 是 pcBuf 的長度 */
/* 接收消息 */
/* MSG_BUF_S 消息類型是msgrcv 規定的格式*/
MSG_BUF_S *pstMsg = NULL;
pstMsg = (MSG_BUF_S*)pcBuffer; /* pcBuffer malloc 的緩沖區 */
pstMsg->lMtype = 1;
iLength = msgrcv(iFd, pstMsg, (size_t)(UINT)iBuflen, (LONG)1, IPC_NOWAIT|MSG_NOERROR);
/* 将 msg 拷貝至 buf */
memcpy(pcBuf, pstMsg->acMtext, (size_t)(UINT)iLength);
/* 然後讀取消息 */
參考:
我的部落格:Linux-程序間通信(1)消息隊列 msg
https://blog.csdn.net/lqy971966/article/details/101049563
1.3 信号量(Semaphore) - 同一主機上的程序通信方式
信号量是一個計數器,可以用來控制多個程序對共享資源的通路。
它常作為一種鎖機制,防止某程序正在通路共享資源時,其他程序也通路該資源。
是以,主要作為程序間以及同一程序内不同線程之間的同步手段。
1.3.1 信号量接口 - semget semctl semop
信号量的建立
int semget(key_t key, int nsems, int semflg);
信号量的初始化/删除
int semctl(int semid, int semnum, int cmd, ...);
信号量操作, P操作,V操作,都是通過一個函數實作的
int semop(int semid, struct sembuf *sops, unsigned nsops);
信号量的删除
semctl(int sem_id)
1.4 信号(Signal) - 同一主機上的程序通信方式
信号是作業系統響應某些條件而産生的一個事件
1.4.1 常用信号
SIGALRM 定時器信号
SIGINT 終端終端ctrl+c
SIGKILL 終止信号
SIGHUP 連結中斷
SIGABORT 程序異常終止
SIGQUIT 終端退出
SIGTERM 終止
SIGSEGV 無效記憶體段通路
SIGCHLD 子程序停止或退出
SIGSTOP 停止執行
SIGSTP 終端挂起
1.4.2 信号處理的幾個函數: Signal Sigaction kill 和 alarm
Signal功能:處理指定的信号,主要是處理忽略(SIG_IGN)和恢複(SIG_DFL)
igaction功能:處理指定的信号,比Signal函數更加健壯的信号處理接口
kill功能:程序可以通過kill函數向包括它本身在内的其他程序發送一個信号
alarm功能:提供了一個鬧鐘的功能;程序可以通過調用alarm函數在經過預定的時間後向程序發送一個SIGALRM信号。
參考:
我的部落格:通俗易懂說信号
https://blog.csdn.net/lqy971966/article/details/88938079
1.5 管道(PIPE) - 同一主機上的程序通信方式
管道本質上是核心的一塊緩存
1.5.1 匿名管道
匿名管道是基于檔案描述符的通信方式。實作兩個程序間的通信時必須通過fork建立子程序,實作父子程序之間的通信
-
特點
隻能夠進行單向通信
隻能夠用于有血緣關系(父子,兄弟,爺孫)的程序之間,多常用于父子之間
int mkfifo(const char *filename,mode_t mode); 【參數】: filename:建立的有名管道的全路徑名 mode:建立的命名管道的模式,指明其存取權限
1.5.2 命名管道
命名管道本質上是一個管道檔案,可以通過指令建立也可以通過函數建立,使用者可以看到
-
特點
可以進行不相幹程序間的通信
命名管道是一個檔案,對于檔案的相關操作對其同樣适用
1.5.3 總結
類型 程序關系 不同點 本質
匿名管道 必須是親緣關系 由pipe建立并打開 核心的一塊緩存
命名管道 兩個毫不相幹程序 由mkfifo建立,open打開 一個檔案
管道參考:
https://www.linuxprobe.com/linux-process-method.html
https://blog.csdn.net/ljlstart/article/details/48790341
2. 線程間通信
線程間的通信目的主要是用于線程同步,是以線程沒有像程序通信中的用于資料交換的通信機制。
2.1 互斥量+條件變量
互斥量用來確定一個線程獨占一個資源的通路
互斥鎖一個明顯的缺點是他隻有兩種狀态:鎖定和非鎖定,是以經常導緻死鎖。
一個/多個線程等待"條件變量的條件成立"而挂起;另一個線程使"條件成立"信号。
條件變量通過允許線程阻塞和等待另一個線程發送信号的方法彌補了互斥鎖的不足,他常和互斥鎖一起使用。
2.1.1 僞代碼示例:
In Thread1:消費者
pthread_mutex_lock(&mutex); // 拿到互斥鎖,進入臨界區
while( 條件為假)
pthread_cond_wait(cond, mutex); // 令程序等待在條件變量上
//修改條件
pthread_mutex_unlock(&mutex); // 釋放互斥鎖
In Thread2:生産者
pthread_mutex_lock(&mutex); // 拿到互斥鎖,進入臨界區
//設定條件為真
pthread_cond_signal(cond); // 通知等待在條件變量上的消費者
pthread_mutex_unlock(&mutex); // 釋放互斥鎖
2.1.2 實際代碼示例
定義
typedef struct tagTestThread
{
/* other */
pthread_cond_t stExternalCond;
pthread_mutex_t stExternalLock;
}MSG_THREAD_S;
TEST_THREAD_S *pstThread;
/* 條件變量等待時間 */
struct timespec stTimeOut;
stTimeOut.tv_sec = 1L;
stTimeOut.tv_nsec = 0;
寫消息
(VOID)pthread_mutex_lock(&(pstThread->stExternalLock));
(VOID)pthread_cond_signal(&(pstThread->stExternalCond));
(VOID)pthread_mutex_unlock(&(pstThread->stExternalLock));
等待消息
(VOID)pthread_mutex_lock(&(pstThread->stExternalLock));
(VOID)pthread_cond_timedwait(&(pstThread->stExternalCond),
&(pstThread->stExternalLock),
&stTimeOut);
(VOID)pthread_mutex_unlock(&(pstThread->stExternalLock));
互斥量+條件變量及其參考我的部落格:
多線程(8)多線程同步之互斥量+條件變量(linux實作)
https://blog.csdn.net/lqy971966/article/details/104524126
2.1.3 條件變量始終與互斥鎖一起使用。
2.2 eventfd - 通知/等待機制
主要用于程序或者線程間的通信(如通知/等待機制的實作)
2.2.1 簡單/僞代碼實作:
定義
ievFD = eventfd(0, EFD_SEMAPHORE);
EFD_SEMAPHORE 提供類似信号量語義的 read 操作,簡單說就是計數值 count 遞減 1。
寫
eventfd_t iValue = 1;
/* 消息就緒 */
int iWriteRet = eventfd_write(iEventFd, iValue);
讀
int iReadRet = eventfd_read(iEventFd, &iValue);
/* 讀消息 */
2.2.2 其他eventfd示例及其參考我的部落格
通俗易懂說多路複用(3)eventfd 事件通知
https://blog.csdn.net/lqy971966/article/details/104751751
2.3 信号量
信号量(Semaphore),用來保證兩個或多個關鍵代碼段不被并發調用。
2.3.1 簡單/僞代碼實作:
/* 定義 */
sem_t sem;
sem_init(&sem,0,0);
sem_destroy(&sem);
/* 消費者線程-讀取消息 */
sem_wait(&sem);
/* 讀消息 */
/* 生産者線程-消息就緒 */
/* 寫消息 */
sem_post(&sem);
2.3.2 詳細及示例代碼參考我的部落格
多線程(11)多線程同步之信号量(Linux實作)
https://blog.csdn.net/lqy971966/article/details/104534306
2.4 信号
Linux 用 pthread_kill 對線程發信号。
pthread_cancel 線程的取消
2.5 鎖機制:包括互斥鎖、條件變量、讀寫鎖和自旋鎖。
2.5.1 互斥鎖
互斥鎖確定同一時間隻能有一個線程通路共享資源。
當鎖被占用時試圖對其加鎖的線程都進入阻塞狀态(釋放CPU資源使其由運作狀态進入等待狀态)。
當鎖釋放時哪個等待線程能獲得該鎖取決于核心的排程。
2.5.2 讀寫鎖
讀寫鎖當以寫模式加鎖而處于寫狀态時任何試圖加鎖的線程(不論是讀或寫)都阻塞,
當以讀狀态模式加鎖而處于讀狀态時“讀”線程不阻塞,“寫”線程阻塞。
讀模式共享,寫模式互斥。
2.5.3 條件變量
條件變量可以以原子的方式阻塞程序,直到某個特定條件為真為止。
對條件的測試是在互斥鎖的保護下進行的。
條件變量始終與互斥鎖一起使用。
2.5.4 自旋鎖
自旋鎖上鎖受阻時線程不阻塞而是在循環中輪詢檢視能否獲得該鎖,沒有線程的切換因而沒有切換開銷,不過對CPU的霸占會導緻CPU資源的浪費。
是以自旋鎖适用于并行結構(多個處理器)或者适用于鎖被持有時間短而不希望線上程切換産生開銷的情況。
參考:
https://blog.csdn.net/ljlstart/article/details/48790341