當前位置:
首頁 > 最新 > 七大進程間通信和線程同步

七大進程間通信和線程同步

前言

網路編程是Linux C/C++的面試重點,這次我就來聊聊進程間通信和線程同步的問題,可以參看《unix高級環境編程》,希望幫助到大家

正文

1、進程間的七大通信方式

signal、file、pipe、shm、sem、msg、socket

signal:

信號通信的目的:某某事件發生!此時需要處理什麼,進程間(可以是不相關的進程)傳遞信號

場景:信號又被稱之為中斷,需要處理什麼對應的是中斷處理函數,此時設置斷點,形參入棧,保存現場信息,然後去執行中斷處理函數,當處理完成之後,恢復現場信息,程序繼續往下執行

Linux下可以通過kill -l查看其所有信號(其一共64種信號)

發送信號:kill(pid, 信號) //對指定的進程發送什麼信號

raise(信號) kill(getpid(), 信號) //就是給自己發送指定的信號

alarm(秒數) :定時產生一個SIGALRM信號,調用alarm方法之後,只會產生一次該信號

接收信號:signal(信號,函數指針) //對該信號接收,並調用自己的函數指針進行處理

信號通信方式的局限性:不能夠傳遞複雜的、有效的、具體的數據

file:

每打開一個文件,就會產生一個文件控制塊,而文件控制塊與文件描述符是一一對應的,通過對文件描述符的操作進而對文件進行操作

文件描述符的分配原則:編號的連續性(節省編號的資源)

通過文件系統對文件描述符的讀/寫控制,進程間一方對文件寫,一方對文件讀,達到文件之間的通信;可以是不相關進程間的通信

使用的API:write()和read()

為了能夠實現兩個進程通過文件進行有序的數據交流,還得藉助於信號的處理機制

(1)、通過pause()等待對方發起一個信號,已確認可以開始執行下一次讀/寫操作;

pause():只要接受到任何的信號,立馬就可以往下執行

(2)、通過kill(,SIGUSR1)方法向對方發出明確的信號:可以開始下一步執行(讀、寫)

缺點:i、文件通信沒有訪問規則,ii、(因為CPU > 內存 > 文件)是低速的

pipe:

在通信的進程間構建一個單向的數據流動的通道,數據通過管道從一個進程流向另一個進程是具有時間先後順序的,所以是半雙工通信;管道文件是一種臨時文件,不是磁碟上真真正正的文件,是一塊內存區域

分為:

fd[0]:讀出數據

fd[1]:寫入數據

無名管道:只能用於親緣關係的父子進程,fd = pipe(),得到的是管道文件描述符,通過fd,用的是write()和read()讀寫數據;

達到雙方通信:得用2個管道,達到可以發多句話,的fork()子進程處理

有名管道:非父子進程間通信mkfifo()

mkfifo會在文件系統中創建一個管道文件,然後使其映射內存的一個特殊區域,凡是能夠打開mkfifo創建的管道文件進程(通過這個文件描述符),都可以使用該文件實現FIFO的數據流動

mkfifo(文件名, O_CREAT | O_EXCL | 0755);創建了2個管道文件,在客戶端創建一個讀的管道文件,在伺服器創建一個寫的管道文件,然後當做文件操作即可

socketpair可以創建雙向管道,fd[0]、fd[1]都是同時具有讀和寫的屬性;

優點:(1)、有強制的訪問規則FIFO,(2)、用內存模仿文件,也就是用文件的方式操作內存

管道通信的特點:

(1)、如果管道為空,從管道讀取數據的一方會阻塞。直到管道中有新的數據為止

(2)、管道的數據通信具有FIFO特性,這樣可以避免數據的混亂

(3)、管道數據的讀取與發送並沒有次數限制,而是管道是否為空時最重要的指標

(4)、這種管道的使用具有一個最大的局限性:只適用於父子進程之間。從程序的設計中可以看到,管道的創建是父進程完成的,而且是在創建子進程之前,從而才使得子進程擁有了管道文件描述符,才能夠使得父子進程約定持有管道的入口或出口

(5)、一個管道只能實現單向的數據流

使用ipcs可以查看當前系統中IPC資源的情況。ipcrm -m shmid ipcrm -s semid

ipcrm -q msgid

shm:

各個進程都能夠共同訪問的共享的內存區域;是獨立於所有的進程空間之外的地址區域; (不相關)進程之間的通信

進程對於共享內存的操作與管理主要是:

(1)、申請創建一個共享內存區域(操作系統內核是不可能主動為進程創建共享內存的!),操作系統內核得到申請然後創建

(2)、申請使用一個已存在的共享內存區域

(3)、申請釋放共享內存區域(操作系統內核也是不可能主動釋放共享內存區域的!),操作系統內核得到申請然後釋放

說明key_t key

i>、key_t是一個long類型,是IPC資源外部約定的key(關鍵)值,通過key值映射對應的唯一存在的某一個IPC資源

ii>、通過key_t的值就能夠判斷某一個對應的共享內存區域在哪,是否已經創建等等。

iii>、一個key值只能映射一個共享內存區域,但同時還可以映射一個信號量,而且還能同時映射一個消息隊列資源,於是就可以使用一個key值管理三種不同的資源

key_t值的產生,有兩種方式:

i>、把key值寫死; //自己直接寫一個數字即可

ii>、根據文件的inode編號生成。需要調用的API:ftok("./tmp/a.c", 3)方法,該方法是獲取指定文件的inode編號在根據第二個參數計算得到最終的一個整型量。

shm的使用:

i>、建立進程與共享內存的映射關係

ii>、讀/寫(直接使用指針即可

iii>、如果對於共享內存的使用結束,此時就要斷開與共享內存的映射

對於第一步來說,需要使用的API:shmat()方法。

對於第三步來說,需要使用的API:shmdt()方法。

被映射正在使用共享內存是否此時可以執行刪除操作呢

是,雖然可以執行刪除操作,卻不能將其直接刪除掉。而是做了2個操作

i>、將其狀態置為dest(可回收狀態)

當共享內存處於dest(待回收狀態),則將其資源設為"私有"(只能將該共享資源分享給其子進程,其它進程無法創建於該資源的使用),當所有的使用該共享內存的進程都退出,此時操作系統才回收共享內存

共享內存的控制

共享內存的控制信息可以通過shmctl()方法獲取,會保存在struct_shmid_ds結構體中

共享內存的控制主要是shmid_ds,即就是共享內存的控制信息

cmd:看執行什麼操作(1、獲取共享內存信息;2、設置共享內存信息;3、刪除共享內存)

API:int shmctl(int shmid, int cmd, struct shmid_ds *buf)

sem:

原因:進程在訪問共享資源是存在衝突的,必須的有一種強制手段說明這些共享資源的訪問規則------>信號量

sem:表示的是一種共享資源(空閑)的個數,對共享資源的訪問規則

訪問規則:

i>、用一種數量去標識某一種共享資源的個數(空閑)

ii>、當有進程需要訪問對應的共享資源的時候,則需要先查看(申請),根據資源對應的當前可用數量進行申請。(申請所需要使用的資源個數)

iii>、資源的管理者(操作系統內核),就使用當前的資源個數減去要申請的資源個數,結果>=0,表示有可用資源,允許該進程繼續訪問;否則表示資源不可用,則告訴進程(暫停或者立即返回)

iv>、資源數量的變化就表示資源的佔用和釋放。佔用:使得可用資源減少;釋放:使得可用資源增加

創建信號量集:int semid = semget(key_t key, int nsems, int semflg)

初始化信號量:

信號量ID事實上是信號量集合的ID,一個ID對應的是一組信號量。此時就使用信號量ID設置整個信號量集合,這種操作分為2種大的可能性

i>、針對信號量集合中的一個信號量進行設置;信號量集合中的信號量是按照數組的方式被管理起來的,從而可以直接使用信號的數組下標來進行訪問

ii>、針對整個信號量集和進行統一的設置。

需要使用的API:semctl()方法。

int semctl(int semid, int semnum, int cmd, ...);

cmd參數

第四個參數 可變參

如果cmd是GETALL、SETALL、GETVAL、SETVAL...的話,則需要提供第四個參數。第四個參數是一個共用體,這個共用體在程序中必須的自己定義(作用:初始化資源個數),定義格式如下:

信號量的操作:

API:semop()方法。 (op:operator操作)

int semop(int semid, struct sembuf *sops, unsigned nsops);

第二個參數需要藉助結構體struct sembuf:

通過下標直接對其信號量sem_op進行加減即可

信號量的特徵:

如果有進程通過信號量申請共享資源,而且此時資源個數已經小於0,則此時對於該進程,有兩種可能性:等待資源,不等待。

如果此時進程選擇等待資源,則操作系統內核會針對該信號量構建進程等待隊列,將等待的進程加入到該隊列之中。

如果此時有進程釋放資源,則會:(1)、先將資源個數增加;(2)、從等待隊列中抽取第一個進程;(3)、根據此時資源個數和第一個進程需要申請的資源個數進行比較,結果大於0,則喚醒該進程;結果小於0,則讓該進程繼續等待。

所以信號量的操作和共享內存一般聯合使用來達到進程間的通信

msg:

就是在進程間架起通道,從宏觀上看是一樣的,但是管道在位元組流上是連續的,消息隊列在發送數據時,分為一個一個獨立的數據單元,也就是消息體,每個消息體都是固定大小的存儲塊,在位元組流上不連續;

消息隊列與管道不同的地方在於:管道中的數據並沒有分割為一個一個的數據獨立單位,在位元組流上是連續的。然而,消息隊列卻將數據分成了一個一個獨立的數據單位,每一個數據單位被稱為消息體。每一個消息體都是固定大小的存儲塊兒,在位元組流上是不連續的。

創建消息隊列

int msgget(key_t key, int msgflg); //創建0個消息隊列

消息的發送和消息的接收

在發送消息的時候動態的創建消息隊列;

(1)、msgsnd()方法在發送消息的時候,是在消息體結構體中指定,當前的消息發送到消息隊列集合中的哪一個消息隊列上。

(2)、消息體結構體中就必須包含一個type值,type值是long類型,而且還必須是結構體的第一個成員。而結構體中的其他成員都被認為是要發送的消息體數據。

(3)、無論是msgsnd()發送還是msgrcv()接收時,只要操作系統內核發現新提供的type值對應的消息隊列集合中的消息隊列不存在,則立即為其創建該消息隊列

總結:為了能夠順利的發送與接收,發送方與接收方需要約定:i>、同樣的消息體結構體;(2)、發送方與接收方在發送和接收的數據塊兒大小上要與消息結構體的具體數據部分保持一致! 否則:將不會讀出正確的數據

重點注意:

消息結構體被發送的時候,只是發送了消息結構體中成員的值,如果結構體成員是指針,並不會將指針所指向的空間的值發送,而只是發送了指針變數所保存的地址值。數組作為消息體結構體成員是可以的。因為整個數組空間都在消息體結構體中

long mtype制定消息隊列編號,下面的數組才是要發送的數據,計算大小,也是這個數組所申請的空間大小。接收方倒數第二個參數為:mtype的值(制定的消息隊列編號)。

均是指針連接;

在接收的時候的指明是哪個消息隊列進行接收(比發送多了一個參數);

socket:

網路之間不同進程間通信,相當於網路編程部分了

2、線程

(1)、線程的API函數:

(2)、線程間的協作

在一個進程中會出現多個線程會訪問同一個內存區域,因此就需要使用一種線程間的協作手段來處理

線程的同步機制主要有:互斥量,信號量,條件變數

互斥量 :出現了mutex,就為互斥量,為鎖機制

(1)、在一個lock(加鎖)和unlock(解鎖)之間,形成的叫做:臨界區域

線程同步:阻塞別人而完成自己。利用互斥量達到同步,使封鎖區域最小化

(2)、加鎖後,沒有解鎖————>將發生阻塞(不能再進行加鎖)

(3)、利用互斥量,將程序執行的不確定順序變為了確定性的順序

(3)、條件變數

i>靜態初始化:

ii>、動態初始化

分別調用pthread_mutex/cond_init,pthread_mutex/cond_destroy()初始化;

條件變數針對死鎖情況(就是沒有出現unlock),此時調用pthread_cond_wait()方法也可以進行解鎖;也就是說wait()函數會在阻塞之時進行解鎖。

pthread_cond_wait()方法是:在阻塞之時,自動解鎖。

該方法在遇到pthread_cond_signal()時,喚醒等待的wait()方法,但是不直接執行wait()其後的語句,而是接著原先pthread_cond_signal()其後的方法繼續執行,直到遇到pthread_mutex_lock()鎖時,此時,轉到wait()其後的方法執行。

在這裡利用的是pthread_cond_wait()和pthread_cond_signal()方法

3、線程間同步

鎖機制:互斥鎖、條件變數、信號量、讀寫鎖

互斥鎖:提供了以排他方式數據結構被並發修改的方法

讀寫鎖:寫鎖優先搶佔資源,讀鎖允許多個線程共同讀共享數據,而寫鎖操作是互斥的

條件變數:以原子方式阻塞進程,直到某個特定條件為真為止

一般情況下:互斥鎖起保護作用,條件變數和互斥鎖一起使用

總結:線程間通信的目的主要用於線程同步,所以線程沒有像進程通信中的用於數據交換的通信機制

ps:留言功能開通不了,今天剛申請開通一下讚賞功能,試試看

聯繫我的方式:關注公眾號--->聯繫我們--->關於我,歡迎一起討論技術


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 編程劍譜 的精彩文章:

TAG:編程劍譜 |