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

来源: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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...