信號量的本質
是一種數據操作鎖,它本身不具有數據交換的功能,而是通過控制其他的通信資源(文件,外部設備)來實現進程間通信,它本身只是一種外部資源的標誌,信號量在此過程中負責數據操作的互斥,同步等功能.
一:爲什麼要使用信號量
爲了防止因多個程序同時訪問一個共享資源而引發的一系列問題,我們需要一種方法,它可以通過生成並使用令牌來授權,在任意一時刻只能有一個執行線程訪問代碼的臨界區域.臨界區域是指數據更新的代碼需要獨佔式地執行,而信號量就可以提供這樣的一種訪問機制,讓一個臨界區同一時間只有一個線程在訪問它,也就是說信號量是用來調協進程對共享資源的訪問的.其中共享內存的使用就要用到信號量.
二:信號量的工作原理
由於信號量只能進行兩種操作等待和發送信號,即P(sv)和V(sv),他們的行爲是這樣的;
P(sv):如果sv的值大於0,就給它減1,如果它的值爲0,就掛起還進程的執行
V(sv):如果有其他進程因等待sv而被掛起,就讓它恢復運行,如果沒有進程因等待sv而掛起就給它加 1;
三:Linux的信號量機制:
信號量的意圖在於進程間同步,互斥鎖和條件變量的意圖則在於線程間同步.但是信號量也可用於線程間,互斥鎖和條件變量也可用於進程間.
進程同步機制:
爲了能夠有效的控制對個進程之間的溝通過程,保證能夠過程有序的進行,操作系統提供了一定同步機制保證進程之間不會自說自話而是有效的協同工作.常見的同步方式有:互斥鎖,條件變量,讀寫鎖和信號燈:
四:相關函數:
1:semop函數
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
作用是改變信號量的值
sem_id是返回信號量的標識符
2:semctl函數
int semctl(int semid,int semnum,int cmd,…)
控制信號量的信息
描述
semctl()在semid()標識的信號集上,或者改集合的第semnum個新號量上執行cmd指定的控制命令
調用程序必須按照下面的方式定義這個聯合體:
union semun
{
int val;//使用值
struct semid_ds *buf;//IPC_STAT,IPC_SEC使用緩存區
unsigned short*array;//GEETALL,SETALL使用的數組
struct seminfo* __buf;//IPC_INFO(linux特有)使用緩衝區
};
注意:該聯合體沒有定義在任何系統頭文件中,因此的用戶自己聲明.
五:驗證:
1:編寫Makefile文件:
.PHONY:all
all:mysem
mysem:mysem.c test.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f mysem
2:mysem.h文件:
#pragma once
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/sem.h>
#define _PATH_NAME_ "/tmp"
#define _PROJ_ID 0x6666
union semun
{
int val;
struct semid_ds *buf;
unsigned short*array;
struct seminfo *_buf;
};
int Createsem(int nums);//創建信號量
int Initsem(int sems_id,int _which,int val);//初始化
int Psem(int sems_id);
int Vsem(int sems_id);
int Destorysem(int sems_id);
int Getsem();
3:mysem.c文件:
#include"mysem.h"
static int Commsemset(int nums,int flags)
{
key_t _key = ftok(_PATH_NAME_,_PROJ_ID);
if(_key<0)
{
printf("%d:%s\n",errno,strerror(errno));
return -1;
}
int sems_id =semget(_key,nums,flags);
return sems_id;
}
static int Comsemop(int sems_id,int op)
{
struct sembuf _sembuf;
_sembuf.sem_num =0;
_sembuf.sem_op =op;
_sembuf.sem_flg =0;
if(semop(sems_id,&_sembuf,1)<0)
{
printf("%d :%s",errno,strerror(errno));
return -1;
}
return 0;
}
int Createsem(int nums)
{
int flags =IPC_CREAT |IPC_EXCL | 0666;
return Commsemset(nums,flags);
}
int Initsem(int sems_id,int _which,int val)
{
union semun _un;
_un.val = val;
if(semctl(sems_id,_which,SETVAL,_un)<0)
{
printf("%d :%s",errno,strerror(errno));
return -1;
}
return 0;
}
int Getsem()
{
int flags =IPC_CREAT;
int nums =0;
return Commsemset(nums,flags);
}
int Destroysem(int sems_id)
{
if(semctl(sems_id,0,IPC_RMID)<0)
{
printf("%d :%s",errno,strerror(errno));
return -1;
}
return 0;
}
int Psem(int sems_id)
{
return Comsemop(sems_id,-1);
}
int Vsem(int sems_id)
{
return Comsemop(sems_id,1);
}
4:test.c測試函數:
#include"mysem.h"
int main()
{
pid_t ip = fork();
if(ip == 0)
{
//child
printf("i am a child. pid is %d\n",getpid());
usleep(313132);
printf("again i am a child. pid is %d\n",getpid());
usleep(3134);
fflush(stdout);
}
else if(ip<0)
{
printf("fork is failed\n");
}
else{
//father
while(1)
{
printf("i am a father:pid is %d\n",getpid());
usleep(43000);
printf("again i am a father:pid is %d\n",getpid());
usleep(420000);
fflush(stdout);
}
pid_t ret =waitpid(ip,NULL,0);
if(ret == ip)
{
printf("wait is success\n");
}
else{
printf("wait failed\n");
}
}
return 0;
}
結果:
總結:
進程/線程同步機制:
臨界區(Critical),互斥量(Mutex),信號量(Semaphone),事件(Event)
1:臨界區:通過多線程的串行化來訪問公共資源或一段代碼,速度快,適合控制數據訪問.在任意時間值允許一個線程對共享資源進程訪問,如果多個線程試圖訪問公共資源,那麼一個線程進去後,其他試圖進入公共資源的線程將被掛起等待,一直等到臨界區的資源被釋放後,其他資源在可以搶佔.
2:互斥量:採用互斥 ,只有擁有互斥對象的線程才能訪問公共資源,因爲互斥對象只有一個,所以能保證公共資源不會同時被多個線程訪問,互斥不僅negative實現同一程序的公共資源安全共享,還能保證不同應用程序的公共資源安全共享
3:信號量:它允許多個線程在同一時刻訪問統一資源,但是需要限制在同一時刻訪問此資源的最大線程數目,信號量允許多個線程同時使用共享資源,這與操作系統的Pv操作相同.它指出了同時訪問共享資源的線程最大數目.
PV操作及信號量的概念是荷蘭科學家提出,信號量S是一個整數,S大於等於0時刻供併發進程使用的資源實體數,但是S小於0時表示正在等待使用共享資源的進程數.
其中:
P操作申請資源:
(1):S減1;
(2):若減1後仍然大於等於0,則進程繼續執行
(3):若S減1後小於0,則該進程被阻塞後進入信號行對應的隊列中,然後裝入進程調度
V操作釋放資源:
(1):S加1;
(2):若相加結果大於0,進程繼續執行
(3):若相加結果小於等於0,則從該信號的等待隊列中喚醒一個等待進程,然後再返回原進程繼續執行或轉入進程調度
4:事件:通過通知操作的方式保持線程的同步,還可以方便實現對多個線程的優先級比較的操作.