一、Posix有名信號燈
1.posix有名信號燈函數(shù)
函數(shù)sem_open創(chuàng)建一個新的有名信號燈或打開一個已存在的有名信號燈。有名信號燈總是既可用于線程間的同步,又能用于進(jìn)程間的同步。
1. sem_open
名稱::
sem_open
功能:
創(chuàng)建并初始化有名信號燈
頭文件:
#include <semaphore.h>
函數(shù)原形:
sem_t *sem_open(const char *name,int oflag,/*mode_t mode,unsigned int value*/);
參數(shù):
name 信號燈的外部名字
oflag 選擇創(chuàng)建或打開一個現(xiàn)有的信號燈
mode 權(quán)限位
value 信號燈初始值
返回值:
成功時返回指向信號燈的指針,出錯時為SEM_FAILED
oflag參數(shù)能是0、O_CREAT(創(chuàng)建一個信號燈)或O_CREAT|O_EXCL(如果沒有指定的信號燈就創(chuàng)建),如果指定了O_CREAT,那 么第三個和第四個參數(shù)是需要的;其中mode參數(shù)指定權(quán)限位,value參數(shù)指定信號燈的初始值,通常用來指定共享資源的書面。該初始不能超過 SEM_VALUE_MAX,這個常值必須低于為32767。二值信號燈的初始值通常為1,計數(shù)信號燈的初始值則往往大于1。
如果指定了O_CREAT(而沒有指定O_EXCL),那么只有所需的信號燈尚未存在時才初始化他。所需信號燈已存在條件下指定O_CREAT不是個錯 誤。該標(biāo)志的意思僅僅是“如果所需信號燈尚未存在,那就創(chuàng)建并初始化他”。不過所需信號燈等已存在條件下指定O_CREAT|O_EXCL卻是個錯誤。
sem_open返回指向sem_t信號燈的指針,該結(jié)構(gòu)里記錄著當(dāng)前共享資源的數(shù)目。
/*semopen.c*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h> /* For O_* constants */#include <sys/stat.h> /* For mode constants */#include <semaphore.h>
int main(int argc,char**argv)
{
sem_t *sem;
if(argc!=2)
{
printf(“please input a file name!\n”);
exit(1);
}
sem=sem_open(argv[1],O_CREAT,0644,1);
exit(0);
}
2. sem_close
名稱::
sem_close
功能:
關(guān)閉有名信號燈
頭文件:
#include
函數(shù)原形:
int sem_close(sem_t *sem);
參數(shù):
sem 指向信號燈的指針
返回值:
若成功則返回0,否則返回-1。
一個進(jìn)程終止時,內(nèi)核還對其上仍然打開著的所有有名信號燈自動執(zhí)行這樣的信號燈關(guān)閉操作。不論該進(jìn)程是自愿終止的還是非自愿終止的,這種自動關(guān)閉都會發(fā)生。
但應(yīng)注意的是關(guān)閉一個信號燈并沒有將他從系統(tǒng)中刪除。這就是說,Posix有名信號燈至少是隨內(nèi)核持續(xù)的:即使當(dāng)前沒有進(jìn)程打開著某個信號燈,他的值仍然保持。
3. sem_unlink
名稱::
sem_unlink
功能:
從系統(tǒng)中刪除信號燈
頭文件:
#include
函數(shù)原形:
int sem_unlink(count char *name);
參數(shù):
name 信號燈的外部名字
返回值:
若成功則返回0,否則返回-1。
有名信號燈使用sem_unlink從系統(tǒng)中刪除。
每個信號燈有一個引用計數(shù)器記錄當(dāng)前的打開次數(shù),sem_unlink必須等待這個數(shù)為0時才能把name所指的信號燈從文件系統(tǒng)中刪除。也就是要等待最后一個sem_close發(fā)生。
/*semunlink.c*/
#include
#include
#include
#include
#include
int main(int argc,char**argv)
{
sem_t *sem;
int val;
if(argc!=2)
{
printf(“please input a file name!\n”);
exit(1);
}
if((sem_unlink(argv[1]))!=0)
perror(“sem_unlink”);
else
printf(“success”);
exit(0);
}
4. sem_getvalue
名稱::
sem_getvalue
功能:
測試信號燈
頭文件:
#include
函數(shù)原形:
int sem_getvalue(sem_t *sem,int *valp);
參數(shù):
sem 指向信號燈的指針
返回值:
若成功則返回0,否則返回-1。
sem_getvalue在由valp指向的正數(shù)中返回所指定信號燈的當(dāng)前值。如果該信號燈當(dāng)前已上鎖,那么返回值或為0,或為某個負(fù)數(shù),其絕對值就是等待該信號燈解鎖的線程數(shù)。
5. sem_wait/sem_trywait
名稱::
sem_wait/sem_trywait
功能:
等待共享資源
頭文件:
#include
函數(shù)原形:
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
參數(shù):
sem 指向信號燈的指針
返回值:
若成功則返回0,否則返回-1。
我們能用sem_wait來申請共享資源,sem_wait函數(shù)能測試所指定信號燈的值,如果該值大于0,那就將他減1并即時返回。我們就能使用申請來的 共享資源了。如果該值等于0,調(diào)用線程就被進(jìn)入睡眠狀態(tài),直到該值變?yōu)榇笥?,這時再將他減1,函數(shù)隨后返回。sem_wait操作必須是原子的。 sem_wait和sem_trywait的差別是:當(dāng)所指定信號燈的值已是0時,后者并不將調(diào)用線程投入睡眠。相反,他返回一個EAGAIN錯誤。
下面的程式我們先不去運行,稍后再運行。
6. sem_post
名稱::
sem_post
功能:
掛出共享資源
頭文件:
#include
函數(shù)原形:
int sem_post(sem_t *sem);
int sem_getvalue(sem_t *sem,int *valp);
參數(shù):
sem 指向信號燈的指針
返回值:
若成功則返回0,否則返回-1。
當(dāng)一個線程使用完某個信號燈時,他應(yīng)該調(diào)用sem_post來告訴系統(tǒng)申請的資源已用完。本函數(shù)和sem_wait函數(shù)的功能正好相反,他把所指定的信號燈的值加1,然后喚醒正在等待該信號燈值變?yōu)檎龜?shù)的任意線程。
下面的程式我們先不去運行,稍后再運行。
二. 關(guān)于posix有名信號燈使用的幾點注意
我們要注意以下幾點:
1.Posix有名信號燈的值是隨內(nèi)核持續(xù)的。也就是說,一個進(jìn)程創(chuàng)建了一個信號燈,這個進(jìn)程結(jié)束后,這個信號燈還存在,并且信號燈的值也不會改動。
下面我們利用上面的幾個程式來證實這點
#./semopen test
#./semgetvalue test
value=1 信號的值仍然是1
2。當(dāng)持有某個信號燈鎖的進(jìn)程沒有釋放他就終止時,內(nèi)核并不給該信號燈解鎖。
#./semopen test
#./semwait test&
pid 1834 has semaphore,value=0
#./semgetvalue test
value=0 信號量的值變?yōu)?了
3.posix有名信號燈應(yīng)用于多線程
#include
#include
#include
#include
#include
void *thread_function(void *arg); /*線程入口函數(shù)*/
void print(pid_t); /*共享資源函數(shù)*/
sem_t *sem; /*定義Posix有名信號燈*/
int val; /*定義信號燈當(dāng)前值*/
int main(int argc,char *argv[])
{
int n=0;
if(argc!=2)
{
printf(“please input a file name!\n”);
exit(1);
}
sem=sem_open(argv[1],O_CREAT,0644,3); /*打開一個信號燈*/
while(n++循環(huán)創(chuàng)建5個子線程,使他們同步運行*/
{
if((pthread_create(&a_thread,NULL,thread_function,NULL))!=0)
{
perror(“Thread creation failed”);
exit(1);
}
}
pthread_join(a_thread,NULL);
sem_close(bin_sem);
sem_unlink(argv[1]);
}
void *thread_function(void *arg)
{
sem_wait(sem); /*申請信號燈*/
print(); /*調(diào)用共享代碼段*/
sleep(1);
sem_post(sem); /*釋放信號燈*/
printf(“I’m finished,my tid is %d\n”,pthread_self());
}
void print()
{
printf(“I get it,my tid is %d\n”,pthread_self());
sem_getvalue(sem,&val);
printf(“Now the value have %d\n”,val);
}
程式用循環(huán)的方法建立5個線程,然后讓他們調(diào)用同一個線程處理函數(shù)thread_function,在函數(shù)里我們利用信號量來限制訪問共享資源的線程數(shù)。共享資源我們用print函數(shù)來代表,在真正編程中他有能是個終端設(shè)備(如打印機)或是一段有實際意義的代碼。
運行結(jié)果為:
#gcc ?lpthread ?o 8_1 8_1.c
#./8_1 test
I get it,my tid is 1082330304
Now the value have 2
Iget it,my pid is 1894
Now the value have 1
Iget it,my pid is 1895
Now the value have 0
I’m finished,my pid is 1893
I’m finished,my pid is 1894
I’m finished,my pid is 1895
I get it,my pid is 1896
Now the value have 2
I get it,mypid is 1897
Now the value have 1
I’m finished,my pid is 1896
I’m finished,my pid is 1897
4.posix有名信號燈應(yīng)用于多進(jìn)程
下面就是應(yīng)用Posix有名信號燈的一個小程序。用它來限制訪問共享代碼的進(jìn)程數(shù)目。
#include
#include
#include
#include
void print(pid_t);
sem_t *sem; /*定義Posix有名信號燈*/
int val; /*定義信號燈當(dāng)前值*/
int main(int argc,char *argv[])
{
int n=0;
if(argc!=2)
{
printf(“please input a file name!\n”);
exit(1);
}
sem=sem_open(argv[1],O_CREAT,0644,2); /*打開一個信號燈, 初值設(shè)為2*/
while(n++循環(huán)創(chuàng)建5個子進(jìn)程,使它們同步運行*/
{
if(fork()==0)
{
sem_wait(sem); /*申請信號燈*/
print(getpid()); /*調(diào)用共享代碼段*/
sleep(1);
sem_post(sem); /*釋放信號燈*/
printf(“I’m finished,my pid is %d\n”,getpid());
return 0;
}
}
wait(); /*等待所有子進(jìn)程結(jié)束*/
sem_close(sem);
sem_unlink(argv[1]);
exit(0);
}
void print(pid_t pid)
{
printf(“I get it,my pid is %d\n”,pid);
sem_getvalue(sem,&val);
printf(“Now the value have %d\n”,val);
}
程序編譯后運行會得到如下結(jié)果:
#./8_2 8_2.c
I get it,my tid is 1082330304
Now the value have 1
I get it,my tid is 1090718784
Now the value have 0
I finished,my pid is 1082330304
I finished,my pid is 1090718784
I get it,my tid is 1099107264
Now the value have 1
I get it,my tid is 1116841120
Now the value have 0
I finished,my pid is 1099107264
I finished,my pid is 1116841120
I get it,my tid is 1125329600
Now the value have 1
I finished,my pid is 1125329600
三、基于內(nèi)存的信號燈
前面已經(jīng)介紹了Posix有名信號燈。這些信號燈由一個name參數(shù)標(biāo)識,它通常指代文件系統(tǒng)中的某個文件。然而Posix也提供基于內(nèi)存的信號燈,它們由應(yīng)用程序分配信號燈的內(nèi)存空間,然后由系統(tǒng)初始化它們的值。
7.
名稱::
sem_init/sem_destroy
功能:
初始化/關(guān)閉信號等
頭文件:
#include
函數(shù)原形:
int sem_init(sem_t *sem,int shared,unsigned int value);
int sem_getvalue(sem_t *sem);
參數(shù):
sem 指向信號燈的指針
shared 作用范圍
value 信號燈初始值
返回值:
若成功則返回0,否則返回-1。
基于內(nèi)存的信號燈是由sem_init初始化的。sem參數(shù)指向必須由應(yīng)用程序分配的sem_t變量。如果shared為0,那么待初始化的信號燈是在同 一進(jìn)程的各個線程共享的,否則該信號燈是在進(jìn)程間共享的。當(dāng)shared為零時,該信號燈必須存放在即將使用它的所有進(jìn)程都能訪問的某種類型的共享內(nèi)存 中。跟sem_open一樣,value參數(shù)是該信號燈的初始值。
使用完一個基于內(nèi)存的信號燈后,我們調(diào)用sem_destroy關(guān)閉它。
除了sem_open和sem_close外,其它的poisx有名信號燈函數(shù)都可以用于基于內(nèi)存的信號燈。
注意:posix基于內(nèi)存的信號燈和posix有名信號燈有一些區(qū)別,我們必須注意到這些。
1.sem_open不需要類型與shared的參數(shù),有名信號燈總是可以在不同進(jìn)程間共享的。
2.sem_init不使用任何類似于O_CREAT標(biāo)志的東西,也就是說,sem_init總是初始化信號燈的值。因此,對于一個給定的信號燈,我們必須小心保證只調(diào)用一次sem_init。
3.sem_open返回一個指向某個sem_t變量的指針,該變量由函數(shù)本身分配并初始化。但sem_init的第一個參數(shù)是一個指向某個sem_t變量的指針,該變量由調(diào)用者分配,然后由sem_init函數(shù)初始化。
4.posix有名信號燈是通過內(nèi)核持續(xù)的,一個進(jìn)程創(chuàng)建一個信號燈,另外的進(jìn)程可以通過該信號燈的外部名(創(chuàng)建信號燈使用的文件名)來訪問它。 posix基于內(nèi)存的信號燈的持續(xù)性卻是不定的,如果基于內(nèi)存的信號燈是由單個進(jìn)程內(nèi)的各個線程共享的,那么該信號燈就是隨進(jìn)程持續(xù)的,當(dāng)該進(jìn)程終止時它 也會消失。如果某個基于內(nèi)存的信號燈是在不同進(jìn)程間同步的,該信號燈必須存放在共享內(nèi)存區(qū)中,這要只要該共享內(nèi)存區(qū)存在,該信號燈就存在。
5.基于內(nèi)存的信號燈應(yīng)用于線程很麻煩(待會你會知道為什么),而有名信號燈卻很方便,基于內(nèi)存的信號燈比較適合應(yīng)用于一個進(jìn)程的多個線程。
下面是posix基于內(nèi)存的信號燈實現(xiàn)一個進(jìn)程的各個線程間的互次。
#include
#include
#include
#include
#include
#incude
void *thread_function(void *arg); /*線程入口函數(shù)*/
void print(void); /*共享資源函數(shù)*/
sem_t bin_sem; /*定義信號燈*/
int value; /*定義信號量的燈*/
int main()
{
int n=0;
pthread_t a_thread;
if((sem_init(&bin_sem,0,2))!=0) /*初始化信號燈,初始值為2*/
{
perror(“sem_init”);
exit(1);
}
while(n++循環(huán)創(chuàng)建5個線程*/
{
if((pthread_create(&a_thread,NULL,thread_function,NULL))!=0)
{
perror(“Thread creation failed”);
exit(1);
}
}
pthread_join(a_thread,NULL);/*等待子線程返回*/
}
void *thread_function(void *arg)
{
sem_wait(&bin_sem); /*等待信號燈*/
print();
sleep(1);
sem_post(&bin_sem); /*掛出信號燈*/
printf(“I finished,my pid is %d\n”,pthread_self());
pthread_exit(arg);
}
void print()
{
printf(“I get it,my tid is %d\n”,pthread_self());
sem_getvalue(&bin_sem,&value); /*獲取信號燈的值*/
printf(“Now the value have %d\n”,value);
}
posix基于內(nèi)存的信號燈和有名信號燈基本是一樣的,上面的幾點區(qū)別就可以了。
下面是運行結(jié)果:
#gcc –lpthread –o seminitthread seminitthread.c
#./seminitthread
I get it,my tid is 1082330304
Now the value have 1
I get it,my tid is 1090718784
Now the value have 0
I finished,my pid is 1082330304
I finished,my pid is 1090718784
I get it,my tid is 1099107264
Now the value have 1
I get it,my tid is 1116841120
Now the value have 0
I finished,my pid is 1099107264
I finished,my pid is 1116841120
I get it,my tid is 1125329600
Now the value have 1
I finished,my pid is 1125329600
下面的程序并不能得到我們想要的結(jié)果。
#include
#include
#include
#include
void print(pid_t);
sem_t *sem; /*定義Posix有名信號燈*/
int val; /*定義信號燈當(dāng)前值*/
int main(int argc,char *argv[])
{
int n=0;
sem=sem_open(argv[1],O_CREAT,0644,3); /*打開一個信號燈*/
sem_getvalue(sem,&val); /*查看信號燈的值*/
printf(“The value have %d\n”,val);
while(n++循環(huán)創(chuàng)建5個子進(jìn)程,使它們同步運行*/
{
if(fork()==0)
{
sem_wait(sem); /*申請信號燈*/
print(getpid()); /*調(diào)用共享代碼段*/
sleep(1);
sem_post(sem); /*釋放信號燈*/
printf(“I’m finished,my pid is %d\n”,getpid());
return 0;
}
wait(); /*等待所有子進(jìn)程結(jié)束*/
return 0;
}
void print(pid_t pid)
{
printf(“I get it,my pid is %d\n”,pid);
sem_getvalue(sem,&val);
printf(“Now the value have %d\n”,val);
}
下面是運行結(jié)果:
#cc –lpthread –o sem sem.c
#./sem
The value have 3
I get it,my pid is 2236
Now the value have 2
I get it,my pid is 2237
Now the value have 2
I get it,my pid is 2238
Now the value have 2
I get it,my pid is 2239
Now the value have 2
Iget it,my pid is 2240
Now the value have 2
I’m finished,my pid is 2236
I’m finished,my pid is 2237
I’m finished,my pid is 2238
I’m finished,my pid is 2239
I’m finished,my pid is 2240
問題在于sem信號燈不在共享內(nèi)存區(qū)中。fork出來的子進(jìn)程通常不共享父進(jìn)程的內(nèi)存空間。子進(jìn)程是在父進(jìn)程內(nèi)存空間的拷貝上啟動的,它跟共享內(nèi)存不是一回事。