Posix多线程编程(3)—信号灯(量)

 

有名信号灯和基于内存的信号灯

信号灯---限制进程或线程访问共享资源的个数

一、Posix有名信号灯
函数sem_open创建一个新的有名信号灯或打开一个已存在的有名信号灯。有名信号灯总是既可用于线程间的同步,又可以用于进程间的同步。 但是基于内存的信号灯,用于线程间同步比较方便,用于进程间通信就会出现一定的问题。

1.posix
有名信号灯函数
函数1.
名称: sem_open
功能: 创建并初始化有名信号灯

头文件:
#include <semaphore.h>
函数原形:
sem_t *sem_open(const char *name,int oflag,mode_t mode,unsigned int value);
参数: name  信号灯的外部名字 oflag  选择创建或打开一个现有的信号灯 mode 权限位

              value
信号灯初始值

返回值: 成功时返回指向信号灯的指针,出错时为
SEM_FAILED
oflag
参数可以是0O_CREAT(创建一个信号灯)或O_CREAT|O_EXCL(如果没有指定的信号灯就创建),如果指定了O_CREAT,那么第三个和第四个参数是需要的;其中mode参数指定权限位,value参数指定信号灯的初始值,通常用来指定共享资源的书面。该初始不能超过SEM_VALUE_MAX,这个常值必须低于为32767。二值信号灯的初始值通常为1,计数信号灯的初始值则往往大于1

如果指定了O_CREAT(而没有指定O_EXCL),那么只有所需的信号灯尚未存在时才初始化它。所需信号灯已存在条件下指定O_CREAT不是一个错误。该标志的意思仅仅是如果所需信号灯尚未存在,那就创建并初始化它。但是所需信号灯等已存在条件下指定O_CREAT|O_EXCL却是一个错误。

sem_open
返回指向sem_t信号灯的指针,该结构里记录着当前共享资源的数目。

示例:

/*semophore.c*/
#include <semaphore.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.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);
}
#gcc –lpthread –o semopen semopen.c
#./semopen


函数2.
名称: sem_close
功能: 关闭有名信号灯

头文件:
#include <semaphore.h>
函数原形:
int sem_close(sem_t *sem);
参数: sem 指向信号灯的指针

返回值: 若成功则返回0,否则返回-1

一个进程终止时,内核还对其上仍然打开着的所有有名信号灯自动执行这样的信号灯关闭操作。不论该进程是自愿终止的还是非自愿终止的,这种自动关闭都会发生。

但应注意的是关闭一个信号灯并没有将它从系统中删除。这就是说,Posix有名信号灯至少是随内核持续的:即使当前没有进程打开着某个信号灯,它的值仍然保持。


函数3.
名称: sem_unlink
功能: 从系统中删除信号灯

头文件:
#include <semaphore.h>
函数原形:
int sem_unlink(count char *name);
参数: name  信号灯的外部名字

返回值: 若成功则返回0,否则返回-1

有名信号灯使用sem_unlink从系统中删除。

每个信号灯有一个引用计数器记录当前的打开次数,sem_unlink必须等待这个数为0时才能把name所指的信号灯从文件系统中删除。也就是要等待最后一个sem_close发生。

示例:

/*semunlink.c*/
#include <semaphore.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
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
功能: 测试信号灯

头文件:
#include <semaphore.h>
函数原形:
int sem_getvalue(sem_t *sem,int *valp);
参数: sem 指向信号灯的指针

返回值: 若成功则返回0,否则返回-1

sem_getvalue
在由valp指向的正数中返回所指定信号灯的当前值。如果该信号灯当前已上锁,那么返回值或为0,或为某个负数,其绝对值就是等待该信号灯解锁的线程数。

示例:

/*semgetvalue.c*/
#include <semaphore.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int main(int argc,char **argv)
{
sem_t *sem;
int val;
if(argc!=2)
{
    printf(“please input a file name!/n”);
    exit(1);
}
sem=sem_open(argv[1],O_CREAT,0644,1);
sem_getvalue(sem,&val);
printf(“getvalue:value=%d/n”,val);
exit(0);
}

 

函数5.
名称: sem_wait/sem_trywait
功能: 等待共享资源

头文件:
#include <semaphore.h>
函数原形:
int sem_wait(sem_t *sem);
                      int sem_trywait(sem_t *sem);
参数: sem 指向信号灯的指针

返回值: 若成功则返回0,否则返回-1

我们可以用sem_wait来申请共享资源,sem_wait函数可以测试所指定信号灯的值,如果该值大于0,那就将它减1并立即返回。我们就可以使用申请来的共享资源了。如果该值等于0,调用线程就被进入睡眠状态,直到该值变为大于0,这时再将它减1,函数随后返回。sem_wait操作必须是原子的。sem_waitsem_trywait的差别是:当所指定信号灯的值已经是0时,后者并不将调用线程投入睡眠。相反,它返回一个EAGAIN错误。

示例:

下面的程序我们先不去运行,稍后再运行。

/*semwait.c*/
#include <semaphore.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int main(int argc,char **argv)
{
sem_t *sem;
int val;

if(argc!=2)
{
    printf(“please input a file name!/n”);
    exit(1);
}
sem=sem_open(argv[1],O_CREAT,0644,1);

sem_wait(sem);
sem_getvalue(sem,&val);
printf(“pid %ld has semaphore,value=%d/n”,(long)getpid(),val);
pause();
exit(0);
}


函数6.
名称: sem_post
功能: 挂出共享资源

头文件:
#include <semaphore.h>
函数原形:

int sem_post(sem_t *sem);
int sem_getvalue(sem_t *sem,int *valp);
参数: sem 指向信号灯的指针

返回值: 若成功则返回0,否则返回-1

当一个线程使用完某个信号灯时,它应该调用sem_post来告诉系统申请的资源已经用完。本函数和sem_wait函数的功能正好相反,它把所指定的信号灯的值加1,然后唤醒正在等待该信号灯值变为正数的任意线程。

示例:

下面的程序我们先不去运行,稍后再运行。

/*sempost.c*/
#include <semaphore.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

int main(int argc,char **argv)
{
sem_t *sem;
int val;

if(argc!=2)
{
    printf(“please input a file name!/n”);
    exit(1);
}
sem=sem_open(argv[1],O_CREAT,0644,1);

sem_post(sem);
sem_getvalue(sem,&val);
printf(“value=%d/n”, val);
exit(0);
}
2.关于posix有名信号灯使用的几点注意
我们要注意以下几点:
(1).Posix
有名信号灯的值是随内核持续的。也就是说,一个进程创建了一个信号灯,这个进程结束后,这个信号灯还存在,并且信号灯的值也不会改变。

下面我们利用上面的几个程序来证明这点

#./semopen test
#./semgetvalue test
value=1  
信号的值仍然是
1
(2).
当持有某个信号灯锁的进程没有释放它就终止时,内核并不给该信号灯解锁。

#./semopen test
#./semwait test&
pid 1834 has semaphore,value=0
#./semgetvalue test
value=0  
信号量的值变为0


3.posix有名信号灯应用于多线程

一个很好的体现信号量()的例子
#include <semaphore.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <pthread.h>

void *thread_function(void *arg); /*
线程入口函数*/
void print(void); /*
共享资源函数
*/
sem_t *sem; /*
定义Posix有名信号灯
*/
int val; /*
定义信号灯当前值
*/

int main(int argc,char *argv[])
{

Pthread_t a_thread;
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++<5) /*
循环创建5个子线程,使它们同步运行
*/
{
    if((pthread_create(&a_thread,NULL,thread_function,NULL))!=0)
        {
             perror(“Thread creation failed”);
             exit(1);
         }
}
pthread_join(a_thread,NULL);
sem_close(sem);
sem_unlink(argv[1]);
}

void *thread_function(void *arg)
{
sem_wait(sem); /*
申请信号灯,信号灯资源可用,则继续向下执行,不可用,则阻塞*/

/*得到一次信号灯,则信号灯的值减1*/
    print(); /*
调用共享代码段
*/
    sleep(1);
    sem_post(sem); /*
释放信号灯*/

/*释放一次信号灯,则信号灯的值加1*/
    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);
}

程序用循环的方法建立5个线程,然后让它们调用同一个线程处理函数thread_function,在函数里我们利用信号量(信号灯)来限制访问共享资源的线程数。共享资源我们用print函数来代表,在真正编程中它可以是一个终端设备(如打印机)或是一段有实际意义的代码。


运行结果为:

#gcc –lpthread –o 8_1 8_1.c
#./8_1 test
I get it,my tid is 1082330304
Now the value have 2
I get it,my pid is 1894
Now the value have 1
I get 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

二、基于内存的信号灯
前面已经介绍了Posix有名信号灯。这些信号灯由一个name参数标识,它通常指代文件系统中的某个文件。然而Posix也提供基于内存的信号灯,它们由应用程序分配信号灯的内存空间,然后由系统初始化它们的值。

函数7.
名称: sem_init/sem_destroy
功能: 初始化/关闭信号等

头文件:
#include <semaphore.h>
函数原形:

int sem_init(sem_t *sem,int shared,unsigned int value);
int sem_getvalue(sem_t *sem);
参数: sem    指向信号灯的指针 shared  作用范围 value   信号灯初始值

返回值: 若成功则返回0,否则返回-1

基于内存的信号灯是由sem_init初始化的。sem参数指向必须由应用程序分配的sem_t变量。如果shared0,那么待初始化的信号灯是在同一进程的各个线程共享的,否则该信号灯是在进程间共享的。当shared为零时,该信号灯必须存放在即将使用它的所有进程都能访问的某种类型的共享内存中。跟sem_open一样,value参数是该信号灯的初始值。

使用完一个基于内存的信号灯后,我们调用sem_destroy关闭它。

除了sem_opensem_close外,其它的poisx有名信号灯函数都可以用于基于内存的信号灯。

注意:posix基于内存的信号灯和posix有名信号灯有一些区别,我们必须注意到这些。

1.sem_open
不需要类型与shared的参数,有名信号灯总是可以在不同进程间共享的

2.sem_init
不使用任何类似于O_CREAT标志的东西,也就是说,sem_init总是初始化信号灯的值。因此,对于一个给定的信号灯,我们必须小心保证只调用一次sem_init

3.sem_open
返回一个指向某个sem_t变量的指针,该变量由函数本身分配并初始化。但sem_init的第一个参数是一个指向某个sem_t变量的指针,该变量由调用者分配,然后由sem_init函数初始化。

4.posix
有名信号灯是通过内核持续的,一个进程创建一个信号灯,另外的进程可以通过该信号灯的外部名(创建信号灯使用的文件名)来访问它。posix基于内存的信号灯的持续性却是不定的,如果基于内存的信号灯是由单个进程内的各个线程共享的,那么该信号灯就是随进程持续的,当该进程终止时它也会消失。如果某个基于内存的信号灯是在不同进程间同步的,该信号灯必须存放在共享内存区中,这要只要该共享内存区存在,该信号灯就存在。

5.
基于内存的信号灯应用于线程很麻烦(待会你会知道为什么),而有名信号灯却很方便,基于内存的信号灯比较适合应用于一个进程的多个线程

下面是posix基于内存的信号灯实现一个进程的各个线程间的互斥。

#include <semaphore.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <pthread.h>
#incude <stdlib.h>
void *thread_function(void *arg); /*
线程入口函数
*/
void print(void); /*
共享资源函数
*/
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++<5) /*
循环创建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
基于内存的信号灯和有名信号灯基本是一样的,上面的几点区别就可以了。

下面是运行结果:

#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


下面是posix基于内存的信号灯实现各进程间的互斥。但要注意它并不能得到我们想要的结果。

#include <semaphore.h>

#include <unistd.h>

#include <stdio.h>

#include <fcntl.h>

 

void print(pid_t);

sem_t *sem;

int val;

 

int main(int argc,char *argv[])

{

int n=0;

if((sem_init(sem,0,2))!=0)

{

    perror("sem_init");

    exit(1);

}

 

while(n++<5)

{

    if(fork()==0)

    {

        sem_wait(sem);

        print(getpid());

        sleep(1);

        sem_post(sem);

        printf("I'm finished,my pid is %d/n",getpid());

        return 0;

    }

   }

wait();

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);

}
下面是运行结果:

#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信号灯不在共享内存区中。fork出来的子进程通常不共享父进程的内存空间。子进程是在父进程内存空间的拷贝上启动的,它跟共享内存不是一回事。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值