POSIX信号量接口,意在解决XSI信号量接口的几个不足之处:
-
POSIX信号量接口相比于XSI信号量接口,允许更高性能的实现。
-
POSIX信号量接口简单易用:没有信号量集,其中一些接口模仿了我们熟悉的文件系统操作。
-
POSIX信号量删除时的处理更加合理。XSI信号量被删除后,使用该信号量标识符的操作将会出错返回,并将errno设置为EIDRM。而对于POSIX信号量,操作可以继续正常执行,直到对该信号量的最后一个引用被释放。
POSIX信号量有两种形式可供选用:有名和无名。它们的区别在于,如何被创建和销毁,其他方面则完全相同。无名信号量只存在于内存中,并且规定能够访问该内存的进程才能够使用该内存中的信号量。这就意味着,无名信号量只能被这样两种线程使用:(1)来自同一进程的各个线程(2)来自不同进程的各个线程,但是这些进程映射了相同的内存范围到自己的地址空间。相反,有名信号量则是通过名字访问,因此,来自于任何进程的线程,只要知道该有名信号量的名字都可以访问。
Posix信号量工作原理:
如果信号量的非负整形变量S大于零,wait就将其减1,如果S 等于0,wait 就将调用线程阻塞。对于signal 操作,如果有线程在信号量上阻塞(此时S 等于0),signal就会解除对某个等待线程的阻塞,使其从wait 中返回,如果没有线程阻塞在信号量上,signal 就将S加1。
由此可见,S 可以被理解为一种资源的数量,信号量即是通过控制这种资源的分配来实现互斥和同步的。如果把S 设为1,信号量即可实现互斥量的功能。如果S 的值大于1,那么信号量即可使多个线程并发运行。另外,信号量不仅允许使用者申请和释放资源,而且还允许使用者创造资源,这就赋予了信号量实现同步的功能。可见,信号量的功能要比互斥量丰富许多。
Posix信号量API:
一 sem_open创一个新的有名信号量 #include <semaphore.h> sem_t *sem_open(const char *name, int oflag, ... /* mode_t mode, unsigned int value */ ); 返回值:若成功则返回指向信号量的指针,若出错则返回SEM_FAILED
如果使用一个现存的有名信号量,我们只需指定两个参数:信号量名和oflag(oflag取0)。把oflag设置为O_CREAT标志时,如果指定的信号量不存在则新建一个有名信号量;如果指定的信号量已经存在,那么打开使用,无其他额外操作发生
如果我们想要确保我们在创建一个新的信号量,可以把oflag参数设置为:O_CREAT|O_EXCL。如果信号量已经存在的话,这会导致sem_open调用失败。
为了提高移植性,我们在选择信号量名字的时候,必须遵循一定的约定:
-
名字的首字符必须是斜杠(/)。
-
除首字符外,名字中不能再包含其他斜杠(/)。
-
名字的最长长度由实现定义,不应超过_POSIX_NAME_MAX个字符。
二:sem_close关闭一个信号量
#include <semaphore.h> int sem_close(sem_t *sem); 返回值:若成功则返回0,出错返回-1
如果进程还没有调用sem_close就已经退出,那么内核会自动关闭该进程打开的所有信号量。注意,这并不会影响信号量值的状态——例如,如果我们增加了信号量的值,我们退出后这个值不会改变。同样,如果我们调用了sem_close,信号量值也不会受到影响。POSIX信号量机制中并没有如同XSI信号量中的SEM_UNDO标志。
三 sem_unlink销毁一个有名信号量
#include <semaphore.h> int sem_unlink(const char *name); 返回值:若成功则返回0,出错则返回-1
sem_unlink函数移除信号量的名字。如果当前没有打开的对该信号量的引用,那么就销毁它。否则,销毁被推迟到最后一个打开的引用被关闭。
与XSI信号量不同,我们只能对POSIX信号量的值进行加1或减1。对信号量值减1,就类似于对一个二值信号量加锁或请求一个与计数信号量相关的资源。
注意,POSIX信号量并没有区分信号量类型。使用二值信号量还是计数信号量,取决于我们如果对信号进行初始化和使用。如果信号量值只能取0和1,那么它就是一个二值信号量。当一个二值信号量值为1,我们则说它未加锁;若它的值为0,则说它已加锁。
四 调用sem_wait或sem_trywait函数,请求一个信号量(对信号量值执行减1操作)。
#include <semaphore.h> int sem_trywait(sem_t *sem); int sem_wait(sem_t *sem); 两个函数返回值:若成功则返回0,出错则返回-1 如果信号量计数为0,这时如果调用sem_wait函数,将会阻塞。直到成功对信号量计数减1或被一个信号中断,sem_wait函数才会返回。我们可以使用sem_trywait函数以避免阻塞。当我们调用sem_trywait函数时,如果信号量计数为0,sem_trywait会返回-1,并将errno设置为EAGAIN。 还可以阻塞一段有限的时间,这时可以使用sem_timedwait函数。 #include <semaphore.h> #include <time.h> int sem_timedwait(sem_t *restrict sem, const struct timespec *restrict tsptr); 返回值:若成功则返回0,出错则返回-1 tsptr参数指定了希望等待的绝对时间。如果信号量可以被立即减1,那么超时也无所谓,即使你指定了一个已经过去的时间,试图对信号量减1的操作也会成功。如果直到超时,还不能对信号量计数减1,那么sem_timedwait函数将会返回-1,并将errno设置为ETIMEDOUT。 五 调用sem_post函数对信号量值加1。这类似于对一个二值信号量解锁或释放一个与计数信号量有关的资源。 #include <semaphore.h> int sem_post(sem_t *sem); 返回值:若成功则返回0,出错则返回-1 当我们调用sem_post的时,如果此时有因为调用sem_wait或sem_timedwait而阻塞的进程,那么该进程将被唤醒,并且刚刚被sem_post加1的信号量计数紧接着又被sem_wait或sem_timedwait减1。 六 如果想在一个单一内进程使用POSIX信号量,那么就使用无名信号量会更加简单。无名信号量只是创建和销毁有所改变,其他完全和有名信号量一样。我们调用sem_init函数创建一个无名信号量。 #include <semaphore.h> int sem_init(sem_t *sem, int pshared, unsigned int value); 返回值:若成功则返回0,出错返回-1
pshared参数指示我们是否要在多进程之间使用该无名信号量。如果要在多个进程之间使用,则将pshared设置为非0值。value参数指定信号量的初始值。
另外,我们需要声明一个sem_t类型的变量,并把它的地址传给sem_init,以便对该变量进行初始化。如果我们要在两个进程之间使用该无名信号量,我们需要确保sem参数指向这两个进程共享的内存范围内。
七 sem_destroy来销毁无名信号量 #include <semaphore.h> int sem_destroy(sem_t *sem); 返回值:若成功则返回0,出错则返回-1 调用sem_destroy后我们将不能再以sem为参数调用任何信号量函数,除非我们再次使用sem_init对sem进行初始化 八 sem_getvalue函数来获取信号量 #include <semaphore.h> int sem_getvalue(sem_t *sem, int *restrict valp); 返回值:若成功则返回0,出错则返回-1 如果sem_getvalue执行成功,信号量的值将存入valp指向的整型变量中。但是,需要小心,我们刚读出来的信号量值可能会改变(因为我们随时可能会使用该信号量值)。如果不采取额外的同步机制的话,sem_getvalue函数仅仅用来调试。 还是以生产者消费者的模型来看下实现: #include<semaphore.h> #include<unistd.h> #include<stdio.h> #include<pthread.h> static int buff[1]={0}; sem_t put,get; void signal_init() { sem_init(&put,0,1); sem_init(&get,0,0); } void signal_destroy() { sem_destroy(&put); sem_destroy(&get); } void *producer_signal(void *arg) { int i; for(i=1;i<=10;i++) { sem_wait(&put); buff[1]=i; printf("value put int the buffer=%d ",i); sem_post(&get); } pthread_exit((void *)"pthread producer exist "); } void *customer_signal(void *arg) { int i; int value; for(i=1;i<=10;i++) { sem_wait(&get); value=buff[1]; printf("value get from the buffer=%d ",value); sem_post(&put); } pthread_exit((void *)"thread customer exist "); } int main() { void *ret; int ret_value; signal_init(); pthread_t tid_producer,tid_customer; ret_value=pthread_create(&tid_producer,NULL,producer_signal,NULL); pthread_create(&tid_customer,NULL,customer_signal,NULL); pthread_join(tid_producer,&ret); pthread_join(tid_customer,&ret); printf("tid_producer=%d ,ret_value=%d",tid_producer,ret_value); signal_destroy(); return 0; } 1 首先申明一个初始量为1的信号put。同时申明一个初始量为0的信号get。 2 生产者每次都等到put信号大于0,在生产完成之后,同时调用sem_post(get)使得get信号大于0 3 消费者每次都等待get信号大于0,然后取出数据,同时将put信号大于0; 运行结果: