歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> Linux技術

最全面的linux信號量解析

信號量

一.什麼是信號量

信號量的使用主要是用來保護共享資源,使得資源在一個時刻只有一個進程(線程)

所擁有。

信號量的值為正的時候,說明它空閒。所測試的線程可以鎖定而使用它。若為0,說明

它被占用,測試的線程要進入睡眠隊列中,等待被喚醒。

二.信號量的分類

在學習信號量之前,我們必須先知道——Linux提供兩種信號量:

(1) 內核信號量,由內核控制路徑使用

(2) 用戶態進程使用的信號量,這種信號量又分為POSIX信號量和SYSTEM

V信號量。

POSIX信號量又分為有名信號量和無名信號量。

有名信號量,其值保存在文件中, 所以它可以用於線程也可以用於進程間的同步。無名

信號量,其值保存在內存中。

倘若對信號量沒有以上的全面認識的話,你就會很快發現自己在信號量的森林裡迷

失了方向。

三.內核信號量

1.內核信號量的構成

內核信號量類似於自旋鎖,因為當鎖關閉著時,它不允許內核控制路徑繼續進行。然而,

當內核控制路徑試圖獲取內核信號量鎖保護的忙資源時,相應的進程就被掛起。只有在資源

被釋放時,進程才再次變為可運行。

只有可以睡眠的函數才能獲取內核信號量;中斷處理程序和可延遲函數都不能使用內

核信號量。

內核信號量是struct semaphore類型的對象,它在<asm/semaphore.h>中定義:

struct semaphore {

atomic_t count;

int sleepers;

wait_queue_head_t wait;

}

count:相當於信號量的值,大於0,資源空閒;等於0,資源忙,但沒有進程等待這

個保護的資源;小於0,資源不可用,並至少有一個進程等待資源。

wait:存放等待隊列鏈表的地址,當前等待資源的所有睡眠進程都會放在這個鏈表中。

sleepers:存放一個標志,表示是否有一些進程在信號量上睡眠。

2.內核信號量中的等待隊列(刪除,沒有聯系)

上面已經提到了內核信號量使用了等待隊列wait_queue來實現阻塞操作。

當某任務由於沒有某種條件沒有得到滿足時,它就被掛到等待隊列中睡眠。當條件得到滿足

時,該任務就被移出等待隊列,此時並不意味著該任務就被馬上執行,因為它又被移進工

作隊列中等待CPU資源,在適當的時機被調度。

內核信號量是在內部使用等待隊列的,也就是說該等待隊列對用戶是隱藏的,無須用

戶干涉。由用戶真正使用的等待隊列我們將在另外的篇章進行詳解。

3.內核信號量的相關函數

(1)初始化:

void sema_init (struct semaphore *sem, int val);

void init_MUTEX (struct semaphore *sem); //將sem的值置為1,表示資源空閒

void init_MUTEX_LOCKED (struct semaphore *sem); //將sem的值置為0,表示資源忙

(2)申請內核信號量所保護的資源:

void down(struct semaphore * sem); // 可引起睡眠

int down_interruptible(struct semaphore * sem); // down_interruptible能被信號打斷

int down_trylock(struct semaphore * sem); // 非阻塞函數,不會睡眠。無法鎖定資源則

馬上返回

(3)釋放內核信號量所保護的資源:

void up(struct semaphore * sem);

4.內核信號量的使用例程

在驅動程序中,當多個線程同時訪問相同的資源時(驅動中的全局變量時一種典型的

共享資源),可能會引發“競態“,因此我們必須對共享資源進行並發控制。Linux內核中

解決並發控制的最常用方法是自旋鎖與信號量(絕大多數時候作為互斥鎖使用)。

ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off)

{

//獲得信號量

if (down_interruptible(&sem))

{

return - ERESTARTSYS;

}

//將用戶空間的數據復制到內核空間的global_var

if (copy_from_user(&global_var, buf, sizeof(int)))

{

up(&sem);

return - EFAULT;

}

//釋放信號量

up(&sem);

return sizeof(int);

}

四.POSIX 信號量與SYSTEM V信號量的比較

1. 對POSIX來說,信號量是個非負整數。常用於線程間同步。

而SYSTEM V信號量則是一個或多個信號量的集合,它對應的是一個信號量結構體,

這個結構體是為SYSTEM V IPC服務的,信號量只不過是它的一部分。常用於進程間同步。

2.POSIX信號量的引用頭文件是“<semaphore.h>”,而SYSTEM V信號量的引用頭文件是

“<sys/sem.h>”。

3.從使用的角度,System V信號量是復雜的,而Posix信號量是簡單。比如,POSIX信

號量的創建和初始化或PV操作就很非常方便。

五.POSIX信號量詳解

1.無名信號量

無名信號量的創建就像聲明一般的變量一樣簡單,例如:sem_t sem_id。然後再初

始化該無名信號量,之後就可以放心使用了。

無名信號量常用於多線程間的同步,同時也用於相關進程間的同步。也就是說,無名信

號量必須是多個進程(線程)的共享變量,無名信號量要保護的變量也必須是多個進程

(線程)的共享變量,這兩個條件是缺一不可的。

常見的無名信號量相關函數:sem_destroy

int sem_init(sem_t *sem, int pshared, unsigned int value);

1)pshared==0 用於同一多線程的同步;

2)若pshared>0 用於多個相關進程間的同步(即由fork產生的)

int sem_getvalue(sem_t *sem, int *sval);

取回信號量sem的當前值,把該值保存到sval中。

若有1個或更多的線程或進程調用sem_wait阻塞在該信號量上,該函數返回兩種值:

1) 返回0

2) 返回阻塞在該信號量上的進程或線程數目

linux采用返回的第一種策略。

sem_wait(或sem_trywait)相當於P操作,即申請資源。

int sem_wait(sem_t *sem); // 這是一個阻塞的函數

測試所指定信號量的值,它的操作是原子的。

若sem>0,那麼它減1並立即返回。

若sem==0,則睡眠直到sem>0,此時立即減1,然後返回。

int sem_trywait(sem_t *sem); // 非阻塞的函數

其他的行為和sem_wait一樣,除了:

若sem==0,不是睡眠,而是返回一個錯誤EAGAIN。

sem_post相當於V操作,釋放資源。

int sem_post(sem_t *sem);

把指定的信號量sem的值加1;

呼醒正在等待該信號量的任意線程。

注意:在這些函數中,只有sem_post是信號安全的函數,它是可重入函數

(a)無名信號量在多線程間的同步

無名信號量的常見用法是將要保護的變量放在sem_wait和sem_post中間所形成的

臨界區內,這樣該變量就會被保護起來,例如:

#include <pthread.h>

#include <semaphore.h>

#include <sys/types.h>

#include <stdio.h>

#include <unistd.h>

int number; // 被保護的全局變量

sem_t sem_id;

void* thread_one_fun(void *arg)

{

sem_wait(&sem_id);

printf("thread_one have the semaphore\n");

number++;

printf("number = %d\n",number);

sem_post(&sem_id);

}

void* thread_two_fun(void *arg)

{

sem_wait(&sem_id);

printf("thread_two have the semaphore \n");

number--;

printf("number = %d\n",number);

sem_post(&sem_id);

}

int main(int argc,char *argv[])

{

number = 1;

pthread_t id1, id2;

sem_init(&sem_id, 0, 1);

pthread_create(&id1,NULL,thread_one_fun, NULL);

pthread_create(&id2,NULL,thread_two_fun, NULL);

pthread_join(id1,NULL);

pthread_join(id2,NULL);

printf("main,,,\n");

return 0;

}

上面的例程,到底哪個線程先申請到信號量資源,這是隨機的。如果想要某個特定的順

序的話,可以用2個信號量來實現。例如下面的例程是線程1先執行完,然後線程2才繼

續執行,直至結束。

int number; // 被保護的全局變量

sem_t sem_id1, sem_id2;

void* thread_one_fun(void *arg)

{

sem_wait(&sem_id1);

printf("thread_one have the semaphore\n");

number++;

printf("number = %d\n",number);

sem_post(&sem_id2);

}

void* thread_two_fun(void *arg)

{

sem_wait(&sem_id2);

printf("thread_two have the semaphore \n");

number--;

printf("number = %d\n",number);

sem_post(&sem_id1);

}

int main(int argc,char *argv[])

{

number = 1;

pthread_t id1, id2;

sem_init(&sem_id1, 0, 1); // 空閒的

sem_init(&sem_id2, 0, 0); // 忙的

pthread_create(&id1,NULL,thread_one_fun, NULL);

pthread_create(&id2,NULL,thread_two_fun, NULL);

pthread_join(id1,NULL);

pthread_join(id2,NULL);

printf("main,,,\n");

return 0;

}

(b)無名信號量在相關進程間的同步

說是相關進程,是因為本程序中共有2個進程,其中一個是另外一個的子進程(由

fork

產生)的。

本來對於fork來說,子進程只繼承了父進程的代碼副本,mutex理應在父子進程

中是相互獨立的兩個變量,但由於在初始化mutex的時候,由pshared = 1指

定了mutex處於共享內存區域,所以此時mutex變成了父子進程共享的一個變

量。此時,mutex就可以用來同步相關進程了。

#include <semaphore.h>

#include <stdio.h>

#include <errno.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <sys/mman.h>

int main(int argc, char **argv)

{

int fd, i,count=0,nloop=10,zero=0,*ptr;

sem_t mutex;

//open a file and map it into memory

fd = open("log.txt",O_RDWR|O_CREAT,S_IRWXU);

write(fd,&zero,sizeof(int));

ptr = mmap( NULL,sizeof(int),PROT_READ |

PROT_WRITE,MAP_SHARED,fd,0 );

close(fd);

/* create, initialize semaphore */

if( sem_init(&mutex,1,1) < 0) //

{

perror("semaphore initilization");

exit(0);

}

if (fork() == 0)

{ /* child process*/

for (i = 0; i < nloop; i++)

{

sem_wait(&mutex);

printf("child: %d\n", (*ptr)++);

sem_post(&mutex);

}

exit(0);

}

/* back to parent process */

for (i = 0; i < nloop; i++)

{

sem_wait(&mutex);

printf("parent: %d\n", (*ptr)++);

sem_post(&mutex);

}

exit(0);

}

2.有名信號量

有名信號量的特點是把信號量的值保存在文件中。

這決定了它的用途非常廣:既可以用於線程,也可以用於相關進程間,甚至是不相關

進程。

(a)有名信號量能在進程間共享的原因

由於有名信號量的值是保存在文件中的,所以對於相關進程來說,子進程是繼承了父

進程的文件描述符,那麼子進程所繼承的文件描述符所指向的文件是和父進程一樣的,當

然文件裡面保存的有名信號量值就共享了。

(b)有名信號量相關函數說明

有名信號量在使用的時候,和無名信號量共享sem_wait和sem_post函數。

區別是有名信號量使用sem_open代替sem_init,另外在結束的時候要像關閉文件

一樣去關閉這個有名信號量。

(1)打開一個已存在的有名信號量,或創建並初始化一個有名信號量。一個單一的調用就完

成了信號量的創建、初始化和權限的設置。

sem_t *sem_open(const char *name, int oflag, mode_t mode , int value);

name是文件的路徑名;

Oflag 有O_CREAT或O_CREAT|EXCL兩個取值;

mode_t控制新的信號量的訪問權限;

Value指定信號量的初始化值。

注意:

這裡的name不能寫成/tmp/aaa.sem這樣的格式,因為在linux下,sem都是創建

在/dev/shm目錄下。你可以將name寫成“/mysem”或“mysem”,創建出來的文件都

是“/dev/shm/sem.mysem”,千萬不要寫路徑。也千萬不要寫“/tmp/mysem”之類的。

當oflag = O_CREAT時,若name指定的信號量不存在時,則會創建一個,而且後

面的mode和value參數必須有效。若name指定的信號量已存在,則直接打開該信號量,

同時忽略mode和value參數。

當oflag = O_CREAT|O_EXCL時,若name指定的信號量已存在,該函數會直接返

回error。

(2) 一旦你使用了一信號量,銷毀它們就變得很重要。

在做這個之前,要確定所有對這個有名信號量的引用都已經通過sem_close()函數

關閉了,然後只需在退出或是退出處理函數中調用sem_unlink()去刪除系統中的信號量,

注意如果有任何的處理器或是線程引用這個信號量,sem_unlink()函數不會起到任何的作

用。

也就是說,必須是最後一個使用該信號量的進程來執行sem_unlick才有效。因為每個

信號燈有一個引用計數器記錄當前的打開次數,sem_unlink必須等待這個數為0時才能把

name所指的信號燈從文件系統中刪除。也就是要等待最後一個sem_close發生。

(c)有名信號量在無相關進程間的同步

前面已經說過,有名信號量是位於共享內存區的,那麼它要保護的資源也必須是位於

共享內存區,只有這樣才能被無相關的進程所共享。

在下面這個例子中,服務進程和客戶進程都使用shmget和shmat來獲取得一塊共享內

存資源。然後利用有名信號量來對這塊共享內存資源進行互斥保護。

< u>File1: server.c </u>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include <stdio.h>

#include <semaphore.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#define SHMSZ 27

char SEM_NAME[]= "vik";

int main()

{

char ch;

int shmid;

key_t key;

char *shm,*s;

sem_t *mutex;

//name the shared memory segment

key = 1000;

//create & initialize semaphore

mutex = sem_open(SEM_NAME,O_CREAT,0644,1);

if(mutex == SEM_FAILED)

{

perror("unable to create semaphore");

sem_unlink(SEM_NAME);

exit(-1);

}

//create the shared memory segment with this key

shmid = shmget(key,SHMSZ,IPC_CREAT|0666);

if(shmid<0)

{

perror("failure in shmget");

exit(-1);

}

//attach this segment to virtual memory

shm = shmat(shmid,NULL,0);

//start writing into memory

s = shm;

for(ch='A';ch<='Z';ch++)

{

sem_wait(mutex);

*s++ = ch;

sem_post(mutex);

}

//the below loop could be replaced by binary semaphore

while(*shm != '*')

{

sleep(1);

}

sem_close(mutex);

sem_unlink(SEM_NAME);

shmctl(shmid, IPC_RMID, 0);

exit(0);

}

< u>File 2: client.c</u>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include <stdio.h>

#include <semaphore.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#define SHMSZ 27

char SEM_NAME[]= "vik";

int main()

{

char ch;

int shmid;

key_t key;

char *shm,*s;

sem_t *mutex;

//name the shared memory segment

key = 1000;

//create & initialize existing semaphore

mutex = sem_open(SEM_NAME,0,0644,0);

if(mutex == SEM_FAILED)

{

perror("reader:unable to execute semaphore");

sem_close(mutex);

exit(-1);

}

//create the shared memory segment with this key

shmid = shmget(key,SHMSZ,0666);

if(shmid<0)

{

perror("reader:failure in shmget");

exit(-1);

}

//attach this segment to virtual memory

shm = shmat(shmid,NULL,0);

//start reading

s = shm;

for(s=shm;*s!=NULL;s++)

{

sem_wait(mutex);

putchar(*s);

sem_post(mutex);

}

//once done signal exiting of reader:This can be replaced by

another semaphore

*shm = '*';

sem_close(mutex);

shmctl(shmid, IPC_RMID, 0);

exit(0);

}

六.SYSTEM V信號量

這是信號量值的集合,而不是單個信號量。相關的信號量操作函數由<sys/ipc.h>引用。

1.信號量結構體

內核為每個信號量集維護一個信號量結構體,可在<sys/sem.h>找到該定義:

struct semid_ds {

struct ipc_perm sem_perm; /* 信號量集的操作許可權限 */

struct sem *sem_base; /* 某個信號量sem結構數組的指針,當前信號量集

中的每個信號量對應其中一個數組元素 */

ushort sem_nsems; /* sem_base 數組的個數 */

time_t sem_otime; /* 最後一次成功修改信號量數組的時間 */

time_t sem_ctime; /* 成功創建時間 */

};

struct sem {

ushort semval; /* 信號量的當前值 */

short sempid; /* 最後一次返回該信號量的進程ID 號 */

ushort semncnt; /* 等待semval大於當前值的進程個數 */

ushort semzcnt; /* 等待semval變成0的進程個數 */

};

2.常見的SYSTEM V信號量函數

(a)關鍵字和描述符

SYSTEM V信號量是SYSTEM V IPC(即SYSTEM V進程間通信)的組成部分,其他

的有SYSTEM V消息隊列,SYSTEM V共享內存。而關鍵字和IPC描述符無疑是它們的共

同點,也使用它們,就不得不先對它們進行熟悉。這裡只對SYSTEM V信號量進行討論。

IPC描述符相當於引用ID號,要想使用SYSTEM V信號量(或MSG、SHM),就必須

用IPC描述符來調用信號量。而IPC描述符是內核動態提供的(通過semget來獲取),用

戶無法讓服務器和客戶事先認可共同使用哪個描述符,所以有時候就需要到關鍵字KEY來

定位描述符。

某個KEY只會固定對應一個描述符(這項轉換工作由內核完成),這樣假如服務器和

客戶事先認可共同使用某個KEY,那麼大家就都能定位到同一個描述符,也就能定位到同

一個信號量,這樣就達到了SYSTEM V信號量在進程間共享的目的。

(b)創建和打開信號量

int semget(key_t key, int nsems, int oflag)

(1) nsems>0 : 創建一個信的信號量集,指定集合中信號量的數量,一旦創建就不能更改。

(2) nsems==0 : 訪問一個已存在的集合

(3) 返回的是一個稱為信號量標識符的整數,semop和semctl函數將使用它。

(4) 創建成功後信號量結構被設置:

.sem_perm 的uid和gid成員被設置成的調用進程的有效用戶ID和有效組ID

.oflag 參數中的讀寫權限位存入sem_perm.mode

.sem_otime 被置為0,sem_ctime被設置為當前時間

.sem_nsems 被置為nsems參數的值

該集合中的每個信號量不初始化,這些結構是在semctl,用參數SET_VAL,SETALL

初始化的。

semget函數執行成功後,就產生了一個由內核維持的類型為semid_ds結構體的信號量

集,返回semid就是指向該信號量集的引索。

(c)關鍵字的獲取

有多種方法使客戶機和服務器在同一IPC結構上會合:

(1) 服務器可以指定關鍵字IPC_PRIVATE創建一個新IPC結構,將返回的標識符存放在某

處(例如一個文件)以便客戶機取用。關鍵字 IPC_PRIVATE保證服務器創建一個新IPC結

構。這種技術的缺點是:服務器要將整型標識符寫到文件中,然後客戶機在此後又要讀文件

取得此標識符。

IPC_PRIVATE關鍵字也可用於父、子關系進程。父進程指定 IPC_PRIVATE創建一個新

IPC結構,所返回的標識符在fork後可由子進程使用。子進程可將此標識符作為exec函數

的一個參數傳給一個新程序。

(2) 在一個公用頭文件中定義一個客戶機和服務器都認可的關鍵字。然後服務器指定此關鍵

字創建一個新的IPC結構。這種方法的問題是該關鍵字可能已與一個 IPC結構相結合,在

此情況下,get函數(msgget、semget或shmget)出錯返回。服務器必須處理這一錯誤,刪除

已存在的IPC結構,然後試著再創建它。當然,這個關鍵字不能被別的程序所占用。

(3) 客戶機和服務器認同一個路徑名和課題I D(課題I D是0 ~ 2 5 5之間的字符值) ,然

後調用函數ftok將這兩個值變換為一個關鍵字。這樣就避免了使用一個已被占用的關鍵字的

問題。

使用ftok並非高枕無憂。有這樣一種例外:服務器使用ftok獲取得一個關鍵字後,該文

件就被刪除了,然後重建。此時客戶端以此重建後的文件來ftok所獲取的關鍵字就和服務器

的關鍵字不一樣了。所以一般商用的軟件都不怎麼用ftok。

一般來說,客戶機和服務器至少共享一個頭文件,所以一個比較簡單的方法是避免使

用ftok,而只是在該頭文件中存放一個大家都知道的關鍵字。

(d)設置信號量的值(PV操作)

int semop(int semid, struct sembuf *opsptr, size_t nops);

(1) semid: 是semget返回的semid

(2)opsptr: 指向信號量操作結構數組

(3) nops : opsptr所指向的數組中的sembuf結構體的個數

struct sembuf {

short sem_num; // 要操作的信號量在信號量集裡的編號,

short sem_op; // 信號量操作

short sem_flg; // 操作表示符

};

(4) 若sem_op 是正數,其值就加到semval上,即釋放信號量控制的資源

若sem_op 是0,那麼調用者希望等到semval變為0,如果semval是0就返回;

若sem_op 是負數,那麼調用者希望等待semval變為大於或等於sem_op的絕對值

例如,當前semval為2,而sem_op = -3,那麼怎麼辦?

注意:semval是指semid_ds中的信號量集中的某個信號量的值

(5) sem_flg

SEM_UNDO 由進程自動釋放信號量

IPC_NOWAIT 不阻塞

到這裡,讀者肯定有個疑惑:semop希望改變的semval到底在哪裡?我們怎麼沒看到

有它的痕跡?其實,前面已經說明了,當使用semget時,就產生了一個由內核維護的信號

量集(當然每個信號量值即semval也是只由內核才能看得到了),用戶能看到的就是返回

的semid。內核通過semop 函數的參數,知道應該去改變semid 所指向的信號量的哪個

semval。

(e)對信號集實行控制操作(semval的賦值等)

int semctl(int semid, int semum, int cmd, ../* union semun arg */);

semid是信號量集合;

semnum是信號在集合中的序號;

semum是一個必須由用戶自定義的結構體,在這裡我們務必弄清楚該結構體的組成:

union semun

{

int val; // cmd == SETVAL

struct semid_ds *buf // cmd == IPC_SET或者 cmd == IPC_STAT

ushort *array; // cmd == SETALL,或 cmd = GETALL

};

val只有cmd ==SETVAL時才有用,此時指定的semval = arg.val。

注意:當cmd == GETVAL時,semctl函數返回的值就是我們想要的semval。千萬不要

以為指定的semval被返回到arg.val中。

array指向一個數組,當cmd==SETALL時,就根據arg.array來將信號量集的所有值都

賦值;當cmd ==GETALL時,就將信號量集的所有值返回到arg.array指定的數組中。

buf 指針只在cmd==IPC_STAT 或IPC_SET 時有用,作用是semid 所指向的信號量集

(semid_ds機構體)。一般情況下不常用,這裡不做談論。

另外,cmd == IPC_RMID還是比較有用的。

(f)例碼

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

#include <stdio.h>

static int nsems;

static int semflg;

static int semid;

int errno=0;

union semun {

int val;

struct semid_ds *buf;

unsigned short *array;

}arg;

int main()

{

struct sembuf sops[2]; //要用到兩個信號量,所以要定義兩個操作數組

int rslt;

unsigned short argarray[80];

arg.array = argarray;

semid = semget(IPC_PRIVATE, 2, 0666);

if(semid < 0 )

{

printf("semget failed. errno: %d\n", errno);

exit(0);

}

//獲取0th信號量的原始值

rslt = semctl(semid, 0, GETVAL);

printf("val = %d\n",rslt);

//初始化0th信號量,然後再讀取,檢查初始化有沒有成功

arg.val = 1; // 同一時間只允許一個占有者

semctl(semid, 0, SETVAL, arg);

rslt = semctl(semid, 0, GETVAL);

printf("val = %d\n",rslt);

sops[0].sem_num = 0;

sops[0].sem_op = -1;

sops[0].sem_flg = 0;

sops[1].sem_num = 1;

sops[1].sem_op = 1;

sops[1].sem_flg = 0;

rslt=semop(semid, sops, 1); //申請0th信號量,嘗試鎖定

if (rslt < 0 )

{

printf("semop failed. errno: %d\n", errno);

exit(0);

}

//可以在這裡對資源進行鎖定

sops[0].sem_op = 1;

semop(semid, sops, 1); //釋放0th信號量

rslt = semctl(semid, 0, GETVAL);

printf("val = %d\n",rslt);

rslt=semctl(semid, 0, GETALL, arg);

if (rslt < 0)

{

printf("semctl failed. errno: %d\n", errno);

exit(0);

}

printf("val1:%d val2: %d\n",(unsigned int)argarray[0],(unsigned int)argarray[1]);

if(semctl(semid, 1, IPC_RMID) == -1)

{

Perror(“semctl failure while clearing reason”);

}

return(0);

}

七.信號量的牛刀小試——生產者與消費者問題

1.問題描述:

有一個長度為N的緩沖池為生產者和消費者所共有,只要緩沖池未滿,生產者便可將

消息送入緩沖池;只要緩沖池未空,消費者便可從緩沖池中取走一個消息。生產者往緩沖池

放信息的時候,消費者不可操作緩沖池,反之亦然。

2.使用多線程和信號量解決該經典問題的互斥

#include <pthread.h>

#include <stdio.h>

#include <semaphore.h>

#define BUFF_SIZE 10

char buffer[BUFF_SIZE];

char count; // 緩沖池裡的信息數目

sem_t sem_mutex; // 生產者和消費者的互斥鎖

sem_t p_sem_mutex; // 空的時候,對消費者不可進

sem_t c_sem_mutex; // 滿的時候,對生產者不可進

void * Producer()

{

while(1)

{

sem_wait(&p_sem_mutex); //當緩沖池未滿時

sem_wait(&sem_mutex); //等待緩沖池空閒

count++;

sem_post(&sem_mutex);

if(count < BUFF_SIZE)//緩沖池未滿

sem_post(&p_sem_mutex);

if(count > 0) //緩沖池不為空

sem_post(&c_sem_mutex);

}

}

void * Consumer()

{

while(1)

{

sem_wait(&c_sem_mutex);//緩沖池未空時

sem_wait(&sem_mutex); //等待緩沖池空閒

count--;

sem_post(&sem_mutex);

if(count > 0)

sem_post(c_sem_nutex);

}

}

int main()

{

pthread_t ptid,ctid;

//initialize the semaphores

sem_init(&empty_sem_mutex,0,1);

sem_init(&full_sem_mutex,0,0);

//creating producer and consumer threads

if(pthread_create(&ptid, NULL,Producer, NULL))

{

printf("\n ERROR creating thread 1");

exit(1);

}

if(pthread_create(&ctid, NULL,Consumer, NULL))

{

printf("\n ERROR creating thread 2");

exit(1);

}

if(pthread_join(ptid, NULL)) /* wait for the producer to finish */

{

printf("\n ERROR joining thread");

exit(1);

}

if(pthread_join(ctid, NULL)) /* wait for consumer to finish */

{

printf("\n ERROR joining thread");

exit(1);

}

sem_destroy(&empty_sem_mutex);

sem_destroy(&full_sem_mutex);

//exit the main thread

pthread_exit(NULL);

return 1;

}

Copyright © Linux教程網 All Rights Reserved