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特有虚拟目录中的内容
在这里插入图片描述

ls命名的输出表明进程的umask覆盖了为other用户指定的read+write权限。

如果再次使用同样的名字来互斥的创建一个信号量,那么这个操作就会失败。因为这个名字已经存在了

在这里插入图片描述

关闭一个信号量

当一个进程打开一个命名信号量时,系统会记录进程与信号量之间的关联关系。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的信号量,然后在后台启动一个递减这个信号量的程序
在这里插入图片描述
后台命令将会阻塞,因为信号量的当前值为0,无法递减这个信号量
在这里插入图片描述
从上面可以看到值 0。在其他一些实现上可能会看到值−1,表示存在一个进程正在等待这个信号量

接着使用一个命令来递增这个信号量,这将会导致后台程序中被阻塞的sem_wait()调用完成执行
在这里插入图片描述
上面输出中的最后一行表明 shell 提示符会与后台作业的输出混合在一起

按下回车后就能看到下一个shell提示符,这也会导致shell报告已终止的后台作业的信息。接着在信号量上执行后续的操作。
在这里插入图片描述

未命名信号量

未命名信号量(也被称为基于内存的信号量)是连续为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 到这个限制之间的任意一个值。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值