天天看點

Unix/Linux程式設計:POSIX信号量概述未命名信号量與其他同步技術比較信号量的限制

概述

信号量是用來同步程序和線程對共享資源的通路的。SUSv3規定了兩種類型的POSIX信号量:

  • 命名信号量:這種信号量擁有一個名字。通過使用相同的名字調用sem_open(),不相關的程序能夠通路同一個信号量。
  • 未命名信号量:這種信号量沒有名字,相反,它位于記憶體中一個預先商定的位置處。當在程序間共享時,信号量必須位于同一個共享記憶體區域中。當線上程間共享時,信号量可以位于被這些線程共享的一塊記憶體區域中(比如堆上或者一個全局變量中)

POSIX 信号量的運作方式與 System V 信号量類似,即 POSIX 信号量是一個整數,其值是不能小于 0 的。如果一個程序試圖将一個信号量的值減少到小于0,那麼取決于所使用的函數,調用會阻塞或傳回一個表明目前無法執行相應操作的産物

命名信号量

要使用命名信号量必須要使用下列函數。

  • sem_open()函數打開或者建立一個信号量并傳回一個句柄以供後繼調用使用,如果這個調用會建立信号量的話還會對所建立的信号量進行初始化。
  • sem_post(sem)和 sem_wait(sem)函數分别遞增和遞減一個信号量值。
  • sem_getvalue()函數擷取一個信号量的目前值。
  • sem_close()函數删除調用程序與它之前打開的一個信号量之間的關聯關系
  • sem_unlink())函數删除一個信号量名字并将其标記為在所有程序關閉該信号量時删除該信号量。

SUSv3 并沒有規定如何實作命名信号量。一些 UNIX 實作将它們建立成位于标準檔案系統上一個特殊位置處的檔案。在 Linux 上,命名信号量被建立成小型 POSIX 共享記憶體對象,其名字的形式為 sem.name,這些對象将被放在一個挂載在/dev/shm 目錄之下的專用 tmpfs 檔案系統中。這個檔案系統具備核心持久性------它包含的信号量對象将會持久,即使目前沒有程序打開它們。除非系統被關閉,否則這些對象将一直存在。

打開一個命名信号量

sem_open()函數建立和打開一個新的命名信号量或打開一個既有信号量

NAME
       sem_open - 初始化并打開一個命名的信号量 

SYNOPSIS
       #include <fcntl.h>           /* For O_* constants */
       #include <sys/stat.h>        /* For mode constants */
       #include <semaphore.h>

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

       Link with -pthread.

DESCRIPTION
       sem_open() 建立一個新的 POSIX 信号量或打開一個現有的信号量。 信号量由名稱辨別。 
       name 的詳細構造見sem_overview(7)。 

       oflag 參數指定控制調用操作的标志。 (标志值的定義可以通過包含 <fcntl.h> 獲得。)
       如果在 oflag 中指定了 O_CREAT,那麼如果信号量尚不存在,則建立該信号量。 信号量的
       所有者(使用者 ID)設定為調用程序的有效使用者 ID。 組所有權(組 ID)設定為調用程序的有
       效組 ID。 如果 O_CREAT 和 O_EXCL 都在 oflag 中指定,那麼如果具有給定名稱的信号
       量已經存在,則傳回錯誤。 

       如果 sem_open()被用來打開一個既有信号量,那麼調用就隻需要兩個參數。但如果在 flags
       中指定了 O_CREAT,那麼就還需要另外兩個參數:mode 和 value。

		mode 參數是一個位掩碼,它指定了施加于新信号量之上的權限, 就像 open(2) 一樣。 (
		權限位的符号定義可以通過包含 <sys/stat.h> 獲得。)并且與 open()一樣,mode 參數
		中的值會根據程序的 umask 來取掩碼。 應該向将通路信号量的每一類使用者授予讀和寫權限。 

	   value 參數是一個無符号整數,它指定了新信号量的初始值。信号量的建立和初始化操作是原子的 ,
	   這樣就避免了 System V 信号量初始化時所需完成的複雜工作了

RETURN VALUE
       成功時,sem_open() 傳回新信号量的位址; 該位址在調用其他信号量相關函數時使用。 
       出現錯誤時,sem_open() 傳回 SEM_FAILED,并設定 errno 以訓示錯誤。 
           

不管是建立一個新信号量還是打開一個既有信号量,sem_open()都會傳回一個指向一個sem_t 值的指針,而在後續的調用中則可以通過這個指針來操作這個信号量。sem_open()在發生錯誤時會傳回 SEM_FAILED 值。

SUSv3 聲稱當在 sem_open()的傳回值指向的 sem_t 變量的副本上執行操作(sem_post()、sem_wait()等)時結果是未定義的。換句話說,像下面這種使用 sem2 的做法是不允許的

sem_t *sp, sem2;
sp = sem_open(...);
sem2 = *sp;
sem_wait(&sem2);
           

通過fork()建立的子程序會繼承其父程序打開的所有命名信号量的引用。在fork()之後,父程序和子程序就能夠使用這些信号量來同步它們的動作了。

看個例子:

//psem_create.c  --- 使用 sem_open()打開或建立一個 POSIX 命名信号量
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.h>
#include <string.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <fcntl.h>
static void
usageError(const char *progName)
{
    fprintf(stderr, "Usage: %s [-cx] name [octal-perms [value]]\n", progName);
    fprintf(stderr, "    -c   Create semaphore (O_CREAT)\n");
    fprintf(stderr, "    -x   Create exclusively (O_EXCL)\n");
    exit(EXIT_FAILURE);
}
int main(int argc, char *argv[])
{
    int flags, opt;
    mode_t perms;
    unsigned int value;
    sem_t *sem;

    flags = 0;
    while ((opt = getopt(argc, argv, "cx")) != -1) {
        switch (opt) {
            case 'c':   flags |= O_CREAT;           break;
            case 'x':   flags |= O_EXCL;            break;
            default:    usageError(argv[0]);
        }
    }

    if (optind >= argc)
        usageError(argv[0]);

    /* Default permissions are rw-------; default semaphore initialization
       value is 0 */

    perms = (argc <= optind + 1) ? (S_IRUSR | S_IWUSR) :
            atoi(argv[optind + 1]);
    value = (argc <= optind + 2) ? 0 : atoi(argv[optind + 2]);

    sem = sem_open(argv[optind], flags, perms, value);
    if (sem == SEM_FAILED){
        perror("sem_open");
        exit(EXIT_FAILURE);
    }


    exit(EXIT_SUCCESS);
}

           

使用效果:首先使用umask指令來否決other使用者的所有權限,然後互斥的建立一個姓名并查了該命名信号量的Linux特有虛拟目錄中的内容

Unix/Linux程式設計:POSIX信号量概述未命名信号量與其他同步技術比較信号量的限制

ls命名的輸出表明程序的umask覆寫了為other使用者指定的read+write權限。

如果再次使用同樣的名字來互斥的建立一個信号量,那麼這個操作就會失敗。因為這個名字已經存在了

Unix/Linux程式設計:POSIX信号量概述未命名信号量與其他同步技術比較信号量的限制

關閉一個信号量

當一個程序打開一個命名信号量時,系統會記錄程序與信号量之間的關聯關系。sem_close()函數會終止這種關聯關系(關閉信号量),釋放系統為該程序關聯到該信号量之上的所有資源,并遞減引用該信号量的程序數。

NAME
       sem_close - close a named semaphore

SYNOPSIS
       #include <semaphore.h>

       int sem_close(sem_t *sem);

       Link with -pthread.

DESCRIPTION
       sem_close() 關閉由 sem 引用的命名信号量,允許釋放系統為該信号量配置設定給調用程序的任何資源。 

RETURN VALUE
       成功時 sem_close() 傳回 0; 

	   錯誤時,傳回 -1,并設定 errno 以訓示錯誤。 

           

打開的命名信号量在程序終止時或者程序執行了一個exec()時會自動被關閉。

關閉一個信号量并不會删除這個信号量,而要删除這個信号量則要使用sem_unlink()

sem_unlink()

sem_unlink()函數删除通過 name 辨別的信号量并将信号量标記成一旦所有程序都使用完這個信号量時就銷毀該信号量

NAME
       sem_unlink - remove a named semaphore

SYNOPSIS
       #include <semaphore.h>

       int sem_unlink(const char *name);

       Link with -pthread.

DESCRIPTION
       sem_unlink() 删除按名稱引用的命名信号量。 
       信号量名稱會立即删除。 
       一旦所有其他打開信号量的程序關閉它,信号量就會被銷毀。 

RETURN VALUE
       成功時 sem_unlink() 傳回 0; 出現錯誤時,傳回 -1,并設定 errno 以訓示錯誤。 
           

使用方法:

int
main(int argc, char *argv[])
{
    if (argc != 2 || strcmp(argv[1], "--help") == 0){
		 printf("%s sem-name\n", argv[0]);
		 exit(EXIT_FAILURE);
	}
       

    if (sem_unlink(argv[1]) == -1){
    	 perror("sem_unlink");
    	 exit(EXIT_FAILURE);
    }
        
    exit(EXIT_SUCCESS);
}
           

等待一個信号量

NAME
       sem_wait, sem_timedwait, sem_trywait - lock a semaphore

SYNOPSIS
       #include <semaphore.h>

       int sem_wait(sem_t *sem);

       int sem_trywait(sem_t *sem);

       int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

       Link with -pthread.

DESCRIPTION
       sem_wait() 遞減(鎖定)sem 指向的信号量。 如果信号量的值大于零,則遞減繼續,函數立即傳回。 
       如果信号量目前的值為零,則調用将阻塞,直到可以執行遞減(即信号量值上升到零以上),或者信号處理
       程式中斷調用。

	   sem_trywait() 與 sem_wait() 相同,隻是如果不能立即執行遞減,則調用傳回錯誤(errno
	   設定為 EAGAIN)而不是阻塞。

	   sem_timedwait() 與 sem_wait() 相同,不同之處在于 abs_timeout 指定了如果不能立即執行
	   遞減調用應阻塞的時間量的限制。 
           

sem_wait()會将sem引用的信号量值減小1

  • 如果信号量的目前值大于 0,那麼 sem_wait()會立即傳回。
  • 如果信号量的目前值等于 0,那麼 sem_wait()會阻塞直到信号量的值大于 0 為止

如果一個阻塞的 sem_wait()調用被一個信号處理器中斷了,那麼它就會失敗并傳回 EINTR錯誤,不管在使用 sigaction()建立這個信号處理器時是否采用了 SA_RESTART 标記。(在其他一些 UNIX 實作上,SA_RESTART 會導緻 sem_wait()自動重新開機。)

// psem_wait.c 使用 sem_wait()來遞減一個 POSIX 信号量 
int
main(int argc, char *argv[])
{
    sem_t *sem;

    if (argc < 2 || strcmp(argv[1], "--help") == 0){
		 printf("%s sem-name\n", argv[0]);
		 exit(EXIT_FAILURE);
	}
       

    sem = sem_open(argv[1], 0);
    if (sem == SEM_FAILED){
		printf("sem_open");
		exit(EXIT_FAILURE);
	}
        

    if (sem_wait(sem) == -1){
		printf("sem_wait");
		exit(EXIT_FAILURE);
	}

    printf("%ld sem_wait() succeeded\n", (long) getpid());
    exit(EXIT_SUCCESS);
}
           

sem_trywait()函數是 sem_wait()的一個非阻塞版本。如果遞減操作無法立即被執行,那麼 sem_trywait()就會失敗并傳回 EAGAIN 錯誤。

int
main(int argc, char *argv[])
{
    if (argc < 2 || strcmp(argv[1], "--help") == 0)
        usageErr("%s sem-name\n", argv[0]);

    sem_t *sem = sem_open(argv[1], 0);
    if (sem == SEM_FAILED){
		printf("sem_open");
		exit(EXIT_FAILURE);
	}

    if (sem_trywait(sem) == -1){
		printf("sem_trywait");
		exit(EXIT_FAILURE);
	}

    exit(EXIT_SUCCESS);
}
           

sem_timedwait()函數是 sem_wait()的另一個變體,它允許調用者為調用被阻塞的時間量指定一個限制,如果 sem_timedwait()調用因逾時而無法遞減信号量,那麼這個調用就會失敗并傳回ETIMEDOUT 錯誤

#define _POSIX_C_SOURCE 199309
#include <semaphore.h>
#include <time.h>
int main(int argc, char *argv[])
{
    if (argc != 3 || strcmp(argv[1], "--help") == 0){
		printf("%s sem-name num-secs\n", argv[0]);
		exit(EXIT_FAILURE);
	}
        

    sem_t *sem = sem_open(argv[1], 0);
    if (sem == SEM_FAILED){
		printf("sem_open");
		exit(EXIT_FAILURE);
	}

    /* sem_timedwait() expects an absolute time in its second argument.
       So we take the number of (relative) seconds specified on the
       command line, and add it to the current system time. */

    struct timespec ts;
    if (clock_gettime(CLOCK_REALTIME, &ts) == -1){
        printf("clock_gettime-CLOCK_REALTIME");
        exit(EXIT_FAILURE);
	}
	
    ts.tv_sec += atoi(argv[2]);

    if (sem_timedwait(sem, &ts) == -1){
		 printf("sem_timedwait");
		 exit(EXIT_FAILURE);
	}
       

    printf("%ld sem_wait() succeeded\n", (long) getpid());
    exit(EXIT_SUCCESS);
}
           

釋出一個信号量

sem_post()函數遞增(增加 1)sem 引用的信号量的值。

NAME
       sem_post - 解鎖信号量 

SYNOPSIS
       #include <semaphore.h>

       int sem_post(sem_t *sem);

       Link with -pthread.

DESCRIPTION
       sem_post() 增加(解鎖)sem 指向的信号量。 如果信号量的值是以變得大于零,
       則在 sem_wait(3) 調用中阻塞的另一個程序或線程将被喚醒并繼續鎖定信号量。 

RETURN VALUE
       成功傳回 0; 出錯時,信号量的值保持不變,傳回-1,并設定errno 以訓示錯誤。 

           

如果在 sem_post()調用之前信号量的值為 0,并且其他某個程序(或線程)正在因等待遞減這個信号量而阻塞,那麼該程序會被喚醒,它的sem_wait()調用會繼續往前執行來遞減這個信号量。

如果多個程序(或線程)在 sem_wait()中阻塞了,并且這些程序的排程采用的是預設的循環時間分享政策,那麼哪個程序會被喚醒并允許遞減這個信号量是不确定的。(與 System V 信号量一樣,POSIX 信号量僅僅是一種同步機制,而不是一種排隊機制)。

SUSv3 規定如果程序或線程執行在實時排程政策下,那麼優先級最高等待時間最長的程序或線程将會被喚醒。

// psem_post 使用 sem_post()遞增一個 POSIX 信号量
int
main(int argc, char *argv[])
{
    sem_t *sem;

    if (argc != 2){
		printf("%s sem-name\n", argv[0]);
		exit(EXIT_FAILURE);
	}
        

    sem = sem_open(argv[1], 0);
    if (sem == SEM_FAILED){
		printf("sem_open");
		exit(EXIT_FAILURE);
	}
        

    if (sem_post(sem) == -1){
		printf("sem_post");
		exit(EXIT_FAILURE);
	}
        
    exit(EXIT_SUCCESS);
}
           

擷取信号量的目前值

sem_getvalue()函數将 sem 引用的信号量的目前值通過 sval 指向的 int 變量傳回。

NAME
       sem_getvalue - 擷取信号量的值 

SYNOPSIS
       #include <semaphore.h>

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

       Link with -pthread.

DESCRIPTION
       sem_getvalue() 将指向 sem 的信号量的目前值放入 sval 指向的整數中。 

       如果一個或多個程序(或線程)目前正在阻塞以等待遞減信号量值,那麼 sval中的 
       傳回值将取決于實作。SUSv3 允許兩種做法:0 或一個絕對值等于在sem_wait()
       中阻塞的等待者數目的負數。Linux 和其他一些實作采用了第一種行為,而另一些
       實作則采用了後一種行為

RETURN VALUE
       成功傳回 0; 出錯時,傳回 -1 并設定 errno 以訓示錯誤。 

           

注意在 sem_getvalue()傳回時,sval 中的傳回值可能已經過時了。依賴于 sem_getvalue()傳回的資訊在執行後續操作時未發生變化的程式将會碰到檢查時、使用時(time-of-check、 time-of-use)的競争條件

// psem_getvalue 使用 sem_getvalue()擷取一個 POSIX 信号量的值
int
main(int argc, char *argv[])
{
    int value;
    sem_t *sem;

    if (argc != 2)
        usageErr("%s sem-name\n", argv[0]);

    sem = sem_open(argv[1], 0);
    if (sem == SEM_FAILED){
		printf("sem_open");
		exit(EXIT_FAILURE);
	}

    if (sem_getvalue(sem, &value) == -1){
		printf("sem_getvalue");
		exit(EXIT_FAILURE);
	}

    printf("%d\n", value);
    exit(EXIT_SUCCESS);
}
           

效果: 首先建立一個初始值為0的信号量,然後在背景啟動一個遞減這個信号量的程式

Unix/Linux程式設計:POSIX信号量概述未命名信号量與其他同步技術比較信号量的限制

背景指令将會阻塞,因為信号量的目前值為0,無法遞減這個信号量

Unix/Linux程式設計:POSIX信号量概述未命名信号量與其他同步技術比較信号量的限制

從上面可以看到值 0。在其他一些實作上可能會看到值−1,表示存在一個程序正在等待這個信号量

接着使用一個指令來遞增這個信号量,這将會導緻背景程式中被阻塞的sem_wait()調用完成執行

Unix/Linux程式設計:POSIX信号量概述未命名信号量與其他同步技術比較信号量的限制

上面輸出中的最後一行表明 shell 提示符會與背景作業的輸出混合在一起

按下回車後就能看到下一個shell提示符,這也會導緻shell報告已終止的背景作業的資訊。接着在信号量上執行後續的操作。

Unix/Linux程式設計:POSIX信号量概述未命名信号量與其他同步技術比較信号量的限制

未命名信号量

未命名信号量(也被稱為基于記憶體的信号量)是連續為sem_t并存儲在應用程式配置設定的記憶體中的變量。通過将這個信号量放在由幾個程序或者線程共享的記憶體區域中就能使得這個信号量對這些程序或者線程可用。

操作未命名信号量所使用的函數與操作命名信号量使用的函數是一樣的(sem_wait()、sem_post()以及 sem_getvalue()等)。此外,還需要用到另外兩個函數。

  • sem_init()對一個信号量進行初始化并通知系統該信号量會在在程序間共享還是在單個程序中的線程間共享。
  • sem_destroy(sem)函數銷毀一個信号量。

這些函數不應該被應用到命名信号量上

未命名與命名信号量對比

使用未命名信号量之後就無需為信号量建立一個名字了,這種做法在下列情況中是比較有用的。

  • 線上程間共享的信号量不需要名字。将一個未命名信号量作為一個共享(全局或堆上的)變量自動會使之對所有線程可用。
  • 在相關程序間共享的信号量不需要名字。如果一個父程序在一塊共享記憶體區域中(如一個共享匿名映射)配置設定了一個未命名信号量,那麼作為 fork()操作的一部分,子程序會自動繼承這個映射,進而繼承這個信号量。

初始化一個未命名信号量

sem_init()函數使用value對sem指定的未命名信号量進行初始化

NAME
       sem_init - initialize an unnamed semaphore

SYNOPSIS
       #include <semaphore.h>

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

       Link with -pthread.

           

pshared表明這個信号量是線上程間共享還是在程序間共享

  • 如果pshared等于0,那麼信号量會在調用程序中的線程間共享。此時,sem通常被指定成一個全局變量的位址會在配置設定在堆上的一個變量的位址。線程共享的信号量具有程序持久性,它在程序終止時會被銷毀。
  • 如果pshared不等于0,那麼信号會在程序間共享。此時,sem必須是共享記憶體區域(一個 POSIX 共享記憶體對象、一個使用 mmap()建立的共享映射、或一個System V 共享記憶體段)中的某個位置的位址。中的某個位置的位址。信号量的持久性與它所處的共享記憶體的持久性是一樣的。(通過其中大部分技術建立的共享記憶體區域具備核心持久性。但共享匿名映射是一個例外,隻要存在一個程序維持着這種映射,那麼它就一直存在下去。)由于fork()建立的子程序會繼承其父程序的記憶體映射,是以程序共享的信号量會被通過fork()建立的子程序繼承。這樣父子程序間就能夠使用這些信号量來同步它們的工作了

之是以需要pshared參數是因為如下原因:

  • 一些實作不支援程序間共享的信号量。在這些系統上為 pshared 指定一個非零值會導緻 sem_init()傳回一個錯誤。Linux 直到核心 2.6 以及 NPTL 線程化技術的出現之後才開始支援未命名的程序間共享的信号量。(在老式的 LinuxThreads 實作中,如果為pshared 指定了一個非零值,那麼 sem_init()就會失敗并傳回一個 ENOSYS 錯誤。)
  • 在同時支援程序間共享信号量和線程間共享信号量的實作上,指定采用何種共享方式是有必要的,因為系統必須要執行特殊的動作來支援所需的共享方式。提供此類資訊還使得系統能夠根據共享的種類來執行優化工作。

NPTL sem_init()實作會忽略 pshared,因為不管采用何種共享方式都無需執行特殊的動作,但可移植的以及面向未來的應用程式應該為 pshared 指定一個恰當的值。

未命名信号量不存在相關的權限設定(即 sem_init()中并不存在在 sem_open()中所需的mode 參數)。對一個未命名信号量的通路将由程序在底層共享記憶體區域上的權限來控制。

SUSv3 規定對一個已初始化過的未命名信号量進行初始化操作将會導緻未定義的行為。換句話說,必須要将應用程式設計成隻有一個程序或線程來調用 sem_init()以初始化一個信号量。

與命名信号量一樣,SUSv3 聲稱在位址通過傳入 sem_init()的 sem 參數指定的 sem_t 變量的副本上執行操作的結果是未定義的,是以應該總是隻在“最初的”信号量上執行操作。

下面程式示範了如何通過 POSXI未命名信号量來保護對全局變量的通路:

#include <semaphore.h>
#include <pthread.h>
static int glob = 0;
static sem_t sem;
static void * threadFunc(void *arg)
{
    int loops = *((int *) arg);
    int loc, j;

    for (j = 0; j < loops; j++) {
        if (sem_wait(&sem) == -1){ // 阻塞等待信号量大于0之後遞減該信号量
			perror("sem_wait");
			exit(EXIT_FAILURE);
		}
            

        loc = glob;
        loc++;
        glob = loc;

        if (sem_post(&sem) == -1){  // 增加信号量
			perror("sem_post");
			exit(EXIT_FAILURE);
		}
            
    }

    return NULL;
}
int
main(int argc, char *argv[])
{
    pthread_t t1, t2;
    int loops, s;

    loops = (argc > 1) ? atoi(argv[1]) : 10000000;

    /* Initialize a semaphore with the value 1 */

    if (sem_init(&sem, 0, 1) == -1){
		perror("sem_init");
		exit(EXIT_FAILURE);
	}
        

    /* Create two threads that increment 'glob' */

    s = pthread_create(&t1, NULL, threadFunc, &loops);
    if (s != 0){
		printf( "%d pthread_create", s);
		exit(EXIT_FAILURE);
	}
        
    s = pthread_create(&t2, NULL, threadFunc, &loops);
    if (s != 0){
		printf( "%d pthread_create", s);
		exit(EXIT_FAILURE);
	}

    /* Wait for threads to terminate */

    s = pthread_join(t1, NULL);
    if (s != 0){
		printf( "%d pthread_join", s);
		exit(EXIT_FAILURE);
	}
    s = pthread_join(t2, NULL);
    if (s != 0){
		printf( "%d pthread_join", s);
		exit(EXIT_FAILURE);
	}

    printf("glob = %d\n", glob);
    exit(EXIT_SUCCESS);
}
           

銷毀一個未命名信号量

sem_destroy()将消耗信号量sem,其中sem必須是一個之前使用sem_init()進行初始化的未命名信号量。隻有不存在程序或者線程在等待一個信号量時才能安全消耗這個信号量。

NAME
       sem_destroy - destroy an unnamed semaphore

SYNOPSIS
       #include <semaphore.h>

       int sem_destroy(sem_t *sem);

       Link with -pthread.


           

當使用sem_destory()銷毀了一個未命名信号量之後就能夠使用sem_init()來重新初始化這個信号量了。

一個未命名信号量應該在其底層的記憶體被釋放之前被消耗。例如,如果信号量一個自動配置設定的變量,那麼在其宿主函數傳回之前就應該銷毀這個信号量。如果信号量位于一個 POSIX共享記憶體區域中,那麼在所有程序都使用完這個信号量以及在使用 shm_unlink()對這個共享記憶體對象執行斷開連結操作之前應該銷毀這個信号量。

在一些實作上,省略 sem_destroy()調用不會導緻問題的發生,但在其他實作上,不調用sem_destroy()會導緻資源洩露。可移植的應用程式應該調用 sem_destroy()以避免此類問題的發生

與其他同步技術比較

POSIX 信号量與 System V 信号量

優點:

  • POSIX IPC 接口更加簡單并且與傳統的 UNIX 檔案模型更加一緻
  • POSIX IPC 對象是引用計數的,這樣就簡化了确定何時删除一個 IPC 對象的工作。
  • POSIX 命名信号量消除了 System V 信号量存在的初始化問題(首先,程序 B 在一個未初始化的信号量(即其值是一個任意值)上執行了一個 semop()。其次,程序 A 中的 semctl()調用覆寫了程序 B 所做出的變更)
  • 将一個 POSIX 未命名信号量與動态配置設定的記憶體對象關聯起來更加簡單:隻需要将信号量嵌入到對象中即可
  • 在高度頻繁地争奪信号量的場景中,那麼 POSIX 信号量的性能與

    System V 信号量的性能是類似的。但在争奪信号量不那麼頻繁的場景中,POSIX 信号量的性能要比 System V 信号量好很多。POSIX 在這種場景中之是以能夠做得更好是因為它們的實作方式隻有在發生争奪的時候才需要執行系統調用,而 System V 信号量操作則不管是否發生争奪都需要執行系統調用。

确定:

  • POSIX 信号量的可移植性稍差
  • POSIX 信号量不支援 System V 信号量中的撤銷特性。(然而這個特性在一些場景中可能并沒有太大的用處。

POSIX 信号量與 Pthreads 互斥體

POSIX 信号量和 Pthreads 互斥體都可以用來同步同一個程序中的線程的動作,并且它們的性能也是相近的。然而互斥體通常是首選方法,因為互斥體的所有權屬性能夠確定代碼具有良好的結構性(隻有鎖住互斥體的線程才能夠對其進行解鎖)。與之形成對比的是,一個線程能夠遞增一個被另一個線程遞減的信号量。這種靈活性會導緻産生結構糟糕的同步設計

信号量的限制

  • SEM_NSEMS_MAX : 這是一個程序能夠擁有的 POSIX 信号量的最大數目。
  • SEM_VALUE_MAX :這是一個 POSIX 信号量值能夠取的最大值。信号量的取值可以為 0 到這個限制之間的任意一個值。

繼續閱讀