進程間通信(三)—信號量

来源:http://www.cnblogs.com/lenomirei/archive/2016/07/07/5649792.html
-Advertisement-
Play Games

我會用幾篇博客總結一下在Linux中進程之間通信的幾種方法,我會把這個開頭的摘要部分在這個系列的每篇博客中都打出來 進程之間通信的方式 管道 消息隊列 信號 信號量 共用存儲區 套接字(socket) 進程間通信(四)—共用存儲區傳送門:http://www.cnblogs.com/lenomire ...


我會用幾篇博客總結一下在Linux中進程之間通信的幾種方法,我會把這個開頭的摘要部分在這個系列的每篇博客中都打出來

進程之間通信的方式

  • 管道
  • 消息隊列
  • 信號
  • 信號量
  • 共用存儲區
  • 套接字(socket)

進程間通信(四)—共用存儲區傳送門:http://www.cnblogs.com/lenomirei/p/5651995.html

進程間通信(二)—消息隊列傳送門:http://www.cnblogs.com/lenomirei/p/5642575.html

進程間通信(一)—管道傳送門:http://www.cnblogs.com/lenomirei/p/5636339.html

第三篇來了!前兩篇訪問量很多,真的是很感謝了

這次記下信號量的相關操作函數和方法,和以前一樣會在博文的最後把測試代碼貼出來!

學過操作系統這本書的話應該對信號量這個名詞不會感到陌生,同時信號和信號量是不同的!

信號量多用於進程間的同步與互斥,簡單的說一下同步和互斥的意思

同步:處理競爭就是同步,安排進程執行的先後順序就是同步,每個進程都有一定的個先後執行順序

互斥:互斥訪問不可共用的臨界資源,同時會引發兩個新的控制問題(互斥可以說是特殊的同步)

競爭:當併發進程競爭使用同一個資源的時候,我們就稱為競爭進程


簡單說一下信號量的工作機制(因為真的很簡單),可以直接理解成計數器(當然其實加鎖的時候肯定不能這麼簡單,不只只是信號量了),信號量會有初值(>0),每當有進程申請使用信號量,通過一個P操作來對信號量進行-1操作,當計數器減到0的時候就說明沒有資源了,其他進程要想訪問就必須等待(具體怎麼等還有說法,比如忙等待或者睡眠),當該進程執行完這段工作(我們稱之為臨界區)之後,就會執行V操作來對信號量進行+1操作。

臨界區:不是個簡單的區域!是加鎖區間的代碼!

臨界資源:只能被一個進程同時使用(不可以多個進程共用),要用到互斥

我們可以說信號量也是進程間通信的一種方式,比如互斥鎖的簡單實現就是信號量,一個進程使用互斥鎖,並通知(通信)其他想要該互斥鎖的進程,阻止他們的訪問和使用(老子正在用!@_@!)

其實信號量的操作函數和之前幾個都是類似的,都是套路

  • 信號量的創建

一定會覺得熟悉的!和消息隊列十分類似

  • 函數原型:int semget(key_t key, int nsems, int semflg);
  • 頭文件:#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>
  • 參數解析
    • key_t這個就不詳細解釋了,在消息隊列篇就講過了
    • nsems這個參數是指創造出幾個信號量,準確的來說,semget這個函數創建出來一個信號量集(就是包含好多信號量的那種,可以簡單的理解為信號量數組),這就是說創建有幾個信號量的信號量集,可以選擇只創建一個
    • semflg還是一樣的,用到兩個參數就夠了IPC_CREAT 和 IPC_EXCL怎麼用的話我在消息隊列篇講過了,就不多費口舌了

下麵會看到信號量的一個缺點

  • 信號量的初始化

這說明信號量的初始化和創建是分開操作的,為什麼是分開的,因為創建的時候沒有給信號量的大小的參數,信號量是可以設置大的(不是只可以設置為1),當然設置為1就是互斥訪問了,比如典型的生產者和消費者問題,但是如果像是讀者寫者中的讀者信號量可不一樣(因為可以有多個讀者同時讀),這裡就不解釋這個模型問題了。

缺點就是,一旦初始化和創建分開之後,就會有線程安全的問題,進程A剛創建一信號量還沒有初始化,進程B便已經開始對該信號量進程P操作(哥哥!我還沒賦值初始化!)根本無法進行-1操作,會阻塞的,我都不知道咋回事(親身體驗該錯誤)。。。

廢話不多說了,函數還是套路

  • 函數原型:int semctl(int semid, int semnum, int cmd, ...);
  • 頭文件:#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>
  • 參數解析
    • 第一個semid就不多說了標識信號量用的,semget成功時候的返回值就是它
    • semnum這個就很有用了,剛纔說了申請的是一個信號量集,這個參數就是告訴函數要初始化的是第幾個信號量,該參數是從0開始的,0表示第一個信號量
    • cmd這次用這個SETVAL表示設置變數

當SETVAL被設置之後,可變參數列表的第四個參數就可以用上了,這裡傳入一個神奇的聯合體(我在手冊里找到了,但是死活用不了它,還是自己寫了一個)

1 union semun {
2                int              val;    /* Value for SETVAL */
3                struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
4                unsigned short  *array;  /* Array for GETALL, SETALL */
5                struct seminfo  *__buf;  /* Buffer for IPC_INFO
6                                            (Linux-specific) */
7            };

就是它!就是它!就看第一個吧,其他我也沒用了(英語我也看不太懂),val就是設置的初值啦,傳入的時候直接傳入就行了,參數就是一個變數(不用傳指針)

這個函數一會刪除我們還會遇到它,ctl就是control控制的簡寫,很多操作都有它

  • 信號量操作

一開始就寫了操作的兩種方式,一種叫P操作,一種叫V操作,都是通過一個函數實現的,這個函數還關聯一個結構體

  • 函數原型:int semop(int semid, struct sembuf *sops, unsigned nsops);
  • 頭文件:#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>
  • 參數解析
    • 第一個semid老朋友了
    • 第二個就是那個結構體了,貼出來看
    • 第三個表示你想同時操作幾個信號量(為什麼不是表示index of信號量呢,在結構體里有一起說),因為信號量集可能有很多信號量,每次一個一個操作不現實,這個參數就是提供一個同時操作的
1 struct sembuf
2 {
3     unsigned short sem_num;  /* semaphore number */
4     short          sem_op;   /* semaphore operation */
5     short          sem_flg;  /* operation flags */
6 };

 

sem_num就是index of信號量,你想操作的信號量的下標,sem_op是個短整型,這裡給1或者-1分別表示V和P操作,而最後一個semflg手冊中提到了兩個

IPC_NOWAIT:一看就是IPC通用的,非等待,不讓忙等待只好去休眠(或者別的,反正不能迴圈等待)

SEM_UNDO:這個一看就是信號量獨有的,看英文也能看出來UNDO是取消之前做過的,什麼意思呢,不得不提到,當進程在臨界區工作(持有鎖)的時候,是不允許掛(掛掉更不好啦)起的!!!這很重要(又要加篇幅啦!@_@),因為一旦掛起或者休眠,有可能就醒不了啦!(掛起有可能等待事件)這樣不好,會讓等待的其他進程饑餓,尤其是掛掉的時候,肯定就更糟糕了,根本就無法執行V操作,等待的進程就會無限的等待下去,遞歸申請信號量也是不好的,尤其是互斥信號量的時候會造成死鎖。這時候UNDO就出來把之前做過的東西都取消掉,P操作就當沒執行過,信號量又從0變回了1,皆大歡喜。

  • 信號量的刪除

semctl又來了,和消息隊列的刪除類似

  • 函數原型:剛纔剛寫了~!
  • 頭文件:剛纔剛寫了~!
  • 參數解析:剛纔剛寫了~!
  • 不一樣的只是把SETVAL為換成IPC_RMID就行了(當然後面的可變參數列表也不要了,可以看一下測試的例子)

 

 

事已至此,基本操作就說完了,廢話少說,show me the code

我的程式分為comm.h(公共頭文件)  comm.c(封裝基本函數) server.c(採用fork完成父子進程間使用信號量) 一共3個文件

基本功能:模擬線程安全(簡單版本),父進程會列印"A""A"子進程會列印"B""B",因為執行順序的原因可能會交叉列印,但我不要這樣,我要讓AA和BB分別連著!完成這個功能

 先看之前的版本

交叉列印不是我想要的

功能完成連續列印

comm.h

 

 1 #include <stdio.h>
 2 #include <sys/ipc.h>
 3 #include <string.h>
 4 #include <unistd.h>
 5 #include <sys//sem.h>
 6 #include <stdlib.h>
 7 #include <errno.h>
 8 
 9 
10 #define _PATH_NAME_ "/tmp"
11 #define _PROJ_ID_ 0x6666
12 
13 static int comm_create_sem(int flags,int num);
14 int create_sem(int num);
15 int get_sem();
16 void P_sem(int sem_id,int index);
17 void V_sem(int sem_id,int index);

 

 

 

comm.c

 

 1 #include "comm.h"
 2 
 3 union semun {
 4                int              val;    /* Value for SETVAL */
 5                struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
 6                unsigned short  *array;  /* Array for GETALL, SETALL */
 7                struct seminfo  *__buf;  /* Buffer for IPC_INFO
 8                                            (Linux-specific) */
 9            };
10 
11 
12 static int comm_create_sem(int flags,int num)
13 {
14   key_t _key=ftok(_PATH_NAME_,_PROJ_ID_);
15   if(_key<0)
16   {
17     printf("%d:%s",errno,strerror(errno));
18   }
19   int sem_id;
20   if((sem_id=semget(_key,num,flags))<0)
21   {
22     printf("semget errno,%d:%s",errno,strerror(errno));
23   }
24   return sem_id;
25 }
26 
27 int create_sem(int num)
28 {
29   int flags=IPC_CREAT | IPC_EXCL;
30   int sem_id=comm_create_sem(flags,num);
31   union semun v;
32   v.val=1;
33   if(semctl(sem_id,0,SETVAL,v)<0)//init
34   {
35     printf("init error,%d:%s",errno,strerror(errno));
36   }
37   return sem_id;
38 }
39 
40 int get_sem()
41 {
42   int flags=0;
43   return comm_create_sem(flags,0);
44 }
45 
46 
47 void P_sem(int sem_id,int index)
48 {
49   struct sembuf s;
50   s.sem_num=index;
51   s.sem_op=-1;
52   s.sem_flg=0;
53   if(semop(sem_id,&s,1)<0)
54   {
55     printf("op errro,%d:%s",errno,strerror(errno));
56   }
57 }
58 
59 void V_sem(int sem_id,int index)
60 {
61   struct sembuf s;
62   s.sem_num=index;
63   s.sem_op=1;
64   s.sem_flg=0;
65   if(semop(sem_id,&s,1)<0)
66   {
67     printf("op error,%d:%s",errno,strerror(errno));
68   }
69 
70 }
71 
72 void destory_sem(int sem_id)
73 {
74   semctl(sem_id,0,IPC_RMID);
75 }

 

 

server.c

 

 1 #include "comm.h"
 2 
 3 
 4 
 5 int main()
 6 {
 7   int pid;
 8   pid=fork();
 9   if(pid>0)
10   {
11     //father
12     int sem_id=create_sem(1);
13     while(1)
14     {
15       P_sem(sem_id,0);
16       printf("A");
17       fflush(stdout);
18       sleep(1);
19       printf("A");
20       fflush(stdout);
21       V_sem(sem_id,0);
22     }
23   }
24   else
25   {
26     //child
27     while(1)
28     {
29       int sem_id=get_sem();
30       P_sem(sem_id,0);
31       printf("B");
32       fflush(stdout);
33       sleep(1);
34       printf("B");
35       fflush(stdout);
36       V_sem(sem_id,0);
37     }
38   }
39 
40   return 0;
41 }

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 協同過濾推薦(Collaborative Filtering Recommendation) 基於用戶的協同過濾分為兩個步驟: 1. 找到與目標用戶興趣相似的用戶集合 2. 找到這個集合中用戶喜歡的、並且目標用戶沒有聽說過的物品推薦給目標用戶 計算兩個用戶的興趣相似度: 設 N(u) 為用戶 u 喜 ...
  • Mongodb 簡單入門(個人學習小記) 1、安裝並註冊成服務:(示例) E:\DevTools\mongodb3.2.6\bin>mongod.exe --bind_ip 127.0.0.1 --logpath "E:\mongodbDataBase\accountValueBase\log\lo ...
  • 1,執行完全備份 這是直接備份到g盤的情況 backup database 資料庫名 to disk='g:\database.bak' 這是備份到備份設備的情況,with init是初始化,也就是直接覆蓋 bakup database 資料庫名 to mybackup with init 2,執行 ...
  • 我會用幾篇博客總結一下在Linux中進程之間通信的幾種方法,我會把這個開頭的摘要部分在這個系列的每篇博客中都打出來 進程之間通信的方式 管道 消息隊列 信號 信號量 共用存儲區 套接字(socket) 進程間通信(三)—信號量傳送門:http://www.cnblogs.com/lenomirei/ ...
  • 參考博客“Linux啟動文件、設置環境變數的位置”(http://www.2cto.com/os/201305/208251.html) 在不可取的root許可權的時候可以選擇編輯~/.bashrc文件,加入相應的配置代碼,這樣當登錄時以及每次打開新的shell時,該文件都被讀取。 而在可取的root ...
  • 一、下載軟體包 1、下載地址 二、安裝 1、java 2、Android sdk 查看源 根據需要進行安裝 如果google很慢,可以改hosts 3、gradle 加入環境變數 三、編譯測試 參考網址 http://askubuntu.com/questions/464755/how-to-ins ...
  • system函數對返回值的處理,涉及3個階段: 階段1:創建子進程等準備工作。如果失敗,返回-1。 階段2:調用/bin/sh拉起shell腳本,如果拉起失敗或者shell未正常執行結束(參見備註1),原因值被寫入到status的低8~15比特位中。system的man中只說明瞭會寫了127這個值, ...
  • 一. 準備工作 1. 下載ubuntu鏡像文件:ubuntu-12.04.3-desktop-amd64.iso(4G及以上記憶體建議64位),註意這個amd並不是指amd晶元。 2. 下載硬碟分區工具:Acronis Disk Director 11 3. 下載系統引導軟體:EasyBCD2.2 二 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...