POSIX 信号量

在[url=http://aisxyz.iteye.com/admin/blogs/2418777]XSI IPC通信之信号量[/url]一节中提到了 XSI 标准的信号量。POSIX 信号量意在解决 XSI 信号量的以下几个缺陷。
1)POSIX 信号量考虑到了更高性能的实现。
2)POSIX 信号量接口使用更简单:没有信号量集。
3)POSIX 信号量在删除时表现更完美。当一个 XSI 信号量被删除时,使用这个信号量标识符的操作会失败,并将 errno 设置成 EIDRM。而使用 POSIX 信号量时,操作能继续正常工作,直到该信号量的最后一次引用被释放。
POSIX 信号量有命名的和未命名的两种。它们的差异在于创建和销毁的形式上。命名信号量可以被任何已知它们名字的进程中的线程使用。而未命名信号量只存在于内存中,这意味着它们只能应用在同一进程中的线程,或者不同进程中已经映射相同内存内容到它们的地址空间中的线程。
可以使用 sem_open 函数来创建一个新的命名信号量或者使用一个现有的信号量。该函数返回的信号量指针用来传递给其他信号量函数。当完成信号量操作时,可以调用 sem_close 函数来释放任何与信号量相关的资源。sme_unlink 函数则可以用来销毁一个命名信号量。

#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag, ... /* mode_t mode, unsigned int value */);
/* 返回值:若成功,返回指向信号量的指针;若出错,返回 SEM_FAILED */
int sem_close(sem_t *sem);
int sem_unlink(const char *name);
/* 两个函数的返回值:若成功,返回 0;否则,返回 -1 */

sem_open 函数中,当使用一个现有的命名信号量时,只需要指定前两个参数:信号量的名字 name 和 oflag 的 0 值。当 oflag 参数有 O_CREAT 标志时,如果命名信号量不存在,则会创建。如果已经存在,则会被使用,但不会有额外的初始化发生。在指定 O_CREAT 标志时,需要提供后两个额外的参数。其中 mode 参数指定信号量的使用权限,其取值同文件的权限位。赋值给信号量的权限可以被调用者的文件创建屏蔽字修改。另一个参数 value 则指定信号量的初始值,它的取值范围是:0 ~ SEM_VALUE_MAX(见[url=http://aisxyz.iteye.com/admin/blogs/2367287]unix限制[/url]一节)。如果想确保创建的是信号量,可以设置 oflag 参数为 O_CREAT|O_EXCL。这样如果信号量已经存在,会导致 sem_open 函数失败,并将 errno 置为 EEXIST。
为了增加可移植性,命名信号量时必须遵循以下规则。
1)名字的第一个字符应该为“/”,以便在 POSIX 信号量的实现使用了文件系统时消除名字的二义性。
2)名字不应包含多余斜杠以避免实现定义的行为。比如,如果使用了文件系统,那么 /mysem 和 //mysem 会被认为是同一个文件名,但如果没使用,则它们可以被认为不同。
3)信号量名字的最大长度是实现定义的,不应该长于 _POSIX_NAME_MAX 个字符,因为这是使用文件系统的实现能允许的最大名字长度限制。
如果进程没有调用 sem_close 函数而退出,那么内核将自动关闭任何打开的信号量。注意,这不会影响信号量值的状态——如果已经对它进行了增 1 操作,这不会因为退出而改变。类似地,信号量值也不会因为调用了 sem_close 函数而受到影响。
sem_unlink 函数会删除信号量的名字。如果没有打开的信号量引用,则立即销毁。否则,销毁将延迟到最后一个打开的引用关闭。

当想要在单个进程中使用 POSIX 信号量时,使用未命名信号量会更容易。相对于命名信号量,这仅仅需要改变创建和销毁信号量的方式。
可以调用 sem_init 函数来创建一个未命名的信号量。对未命名信号量的使用完成时,可以调用 sem_destroy 函数来丢弃它。

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
/* 两个函数的返回值:若成功,返回 0;否则,返回 -1 */

sem_init 函数中的 pshared 参数为非 0 值时表示可以在多个进程中使用该信号量。value 参数指定了信号量的初始值。sem 参数代表匿名信号量的地址。如果要在多个进程之间使用信号量,需要确保该参数在它们共享的内存范围内。
调用 sme_destroy 函数后,不能再使用任何带有 sem 的信号量函数,除非调用 sem_init 函数重新初始化它。

信号量创建好后,就可以利用下面这些函数来操作了。其中,sem_wait 或者 sem_trywait 函数可以用来实现信号量的减 1 操作。sem_timedwait 函数则可以选择阻塞一段确定的时间。sem_post 函数则可用来使信号量值增 1。sem_getvalue 函数可以用来检索信号量值(Mac OS X 10.6.8 不支持该函数)。

#include <semaphore.h>
int sem_wait(sem_t *sem);
int sem_rywait(sem_t *sem);
int sem_timedwait(sem_t *restrict sem, const struct timespec *restrict tsptr);
int sem_post(sem_t *sem);
int sem_getvalue(sem_t *restrict sem, int *restrict valp);
/* 几个函数的返回值:若成功,返回 0;否则,返回 -1 */

使用 sem_wait 函数时,如果信号量计数是 0 就会发生阻塞,直到成功使信号量减 1 或者被信号中断时才返回。使用 sem_trywait 可以避免阻塞:如果信号量是 0,则不会阻塞,而是立即返回 -1 并将 errno 置为 EAGAIN。sem_timedwait 函数中的 tsptr 参数可以指定绝对时间(基于 CLOCK_REALTIME 时钟)。如果超时到期并且信号量计数没能减 1,该函数将返回 -1,并将 errno 置为 ETIMEDOUT。
调用 sem_post 可以唤醒因调用 sem_wait 等函数而阻塞的其中一个进程,并且被 sem_post 增 1 的信号量计数会再次被 sem_wait 等函数减 1。
sem_getvalue 函数调用成功后,valp 参数就会包含信号量值。不过要注意,该值在读出来后信号量的值可能已经变了。除非使用额外的同步机制来避免这种竞争,否则该函数只能用于调试。
下面这段代码使用 POSIX 信号量来实现了一种锁,该锁能被一个线程加锁而被另一个线程解锁。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

struct slock{
sem_t *semp;
char name[_POSIX_NAME_MAX];
};

struct slock* s_alloc(){
struct slock *sp;
static int cnt;
if((sp=malloc(sizeof(struct slock))) == NULL)
return NULL;
do{
snprintf(sp->name, sizeof(sp->name), "/%ld.%d", (long)getpid(), cnt++);
sp->semp = sem_open(sp->name, O_CREAT|O_EXCL, S_IRWXU, 1);
}while(sp->semp == SEM_FAILED && errno == EEXIST);
if(sp->semp == SEM_FAILED){
free(sp);
return NULL;
}
sem_unlink(sp->name);
return sp;
}

void s_free(struct slock *sp){
sem_close(sp->semp);
free(sp);
}

int s_lock(struct slock *sp){
return sem_wait(sp->semp);
}

int s_trylock(struct slock *sp){
return sem_trywait(sp->semp);
}

int s_unlock(struct slock *sp){
return sem_post(sp->semp);
}

这里根据进程 ID 和计数器来创建名字。注意,这里没必要用互斥量来保护计数器,因为当两个竞争的线程同时调用 s_alloc 并以同一个名字结束时,在调用 sem_open 中使用 O_EXCL 标志将会使其中一个成功而另一个失败,失败的线程会将 errno 设置成 EEXIST,然后会再次尝试。另外,在 s_alloc 函数中打开一个信号量后又断开了它的连接,这销毁了名字,所以其他进程不能再次访问它,同时也简化了进程结束时的清理工作。
### POSIX信号量概述 POSIX信号量是一种用于进程或线程间同步的机制,主要用于控制多个进程或线程对共享资源的访问。POSIX信号量可以分为有名信号量和无名信号量[^1]。 #### 无名信号量 对于无名信号量而言,通常应用于同一进程中不同线程之间的同步。创建和初始化无名信号量需要调用`sem_init()`函数[^2]: ```c #include <semaphore.h> int sem_init(sem_t *sem, int pshared, unsigned int value); ``` - `sem`: 指向要初始化的信号量对象指针。 - `pshared`: 如果设置为0,则表示该信号量仅在同一进程内的线程之间共享;否则可以在不同进程之间共享(具体实现依赖于操作系统支持)。 - `value`: 设置初始计数值。 当不再需要使用此信号量时,应该通过`sem_destroy()`来销毁它以释放相关联的资源。 #### 名字信号量 有名信号量则可以通过名称在不同的进程间传递并被识别出来。创建有名信号量需借助`sem_open()`函数: ```c #include <fcntl.h> /* For O_* constants */ #include <sys/stat.h> /* For mode constants */ #include <semaphore.h> #include <stdio.h> sem_t* sem_open(const char* name, int oflag, ... ); ``` 参数说明如下: - `name`: 是一个字符串形式的名字,在整个系统范围内唯一标识这个信号量; - `oflag`: 表达打开模式,比如O_CREAT用来指示如果不存在就新建等标志位; - 可选参数:如果是设置了O_CREAT标记的话还需要提供mode(权限)以及initial_value作为后续两个可变参输入给定初值。 同样地,关闭有名信号量应当采用`sem_close()`, 并且最终删除信号量应使用`sem_unlink()`. #### 基本操作 无论哪种类型的POSIX信号量都只允许执行两种基本的操作——增加(`sem_post`) 和减少 (`sem_wait`). 当尝试降低信号量的时候,若当前它的值等于零那么调用者会被挂起直到有其他地方增加了信号量使得它可以继续运行下去[^3]. ```c // 减少信号量 (等待) int sem_wait(sem_t *sem); // 增加信号量 (发布/唤醒) int sem_post(sem_t *sem); ``` 这些API提供了简单却强大的工具集去构建复杂的并发程序逻辑结构。 ### 示例代码 下面给出一段简单的C语言例子展示如何利用POSIX无名信号量来进行多线程间的同步: ```c #include <pthread.h> #include <semaphore.h> #include <stdio.h> #include <stdlib.h> #define NUM_THREADS 5 sem_t bin_sem; void *thread_function(void *arg){ printf("Thread %ld is waiting.\n", pthread_self()); // 尝试获取锁 sem_wait(&bin_sem); printf("Thread %ld has acquired the semaphore\n", pthread_self()); sleep(rand()%3); // Simulate work with a random delay // 完成工作后释放锁 sem_post(&bin_sem); return NULL; } int main(){ pthread_t threads[NUM_THREADS]; srand(time(NULL)); // 初始化二元信号量 sem_init(&bin_sem , 0 , 1 ); for(int i=0;i<NUM_THREADS;++i){ pthread_create(&(threads[i]),NULL,&thread_function,NULL); } for(int j=0;j<NUM_THREADS;++j){ pthread_join(threads[j],NULL); } // 销毁信号量 sem_destroy(&bin_sem); return EXIT_SUCCESS; } ``` 这段代码展示了五个线程竞争同一个临界区的过程,每次只有一个线程能够进入临界区进行处理,其余四个将会处于等待状态直至前一个离开为止。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值