linux信号量semaphore的几种使用方法(semop函数的特性)

以下提到的几种应用方式,下面都有示例代码。

注意:有个点容易遗忘的:当semop的实参sops设置>0的操作时,一般要给这个op动作添加SEM_UNDO标志,详情可参考另一篇博文:linux线程通信之信号量。

应用情景一:用信号量打造一个二值信号量(互斥量),也即:任何时刻只允许一个线程访问共享资源。P操作用于占用资源,V操作代表释放资源。

使用信号量,关键是要知道semop函数的特性:

① semop函数的第二形参sops可以以数组地址的形式输入多个动作(称为动作集),man手册上讲,semop函数会按照sops数组的顺序、原子性的执行动作集中的动作,要么一个都不执行,要么全部执行。手册上说的这句话我觉得是有点问题的,“要么全部执行”这句话实际上有个例外:如果动作集中某个动作设置的条件(如等待0)会使得线程堵塞在本函数中(或者本函数出错返回)的话,那么后面的动作就只能等解除堵塞之后才能被执行(堵塞时),或者得不到执行(semop出错返回时)。
② 如果在某时刻有多个线程都在等待互斥信号量的使用权,那么一旦占用该互斥量的线程把它释放后,这多个等待的线程中,只能有一个线程被解除堵塞

ps:当无法获得信号量资源时,semop到底是堵塞,还是设置错误并返回,取决于第四参数是否或了IPC_NOWAIT标志。

 

方法1:
步骤:
(1) 把信号量的值初始化为0(创建信号量之后默认值就是0,该步骤不做也行)
(2) P操作,用semop函数设置线程等待信号量的值semval为0,若不为0则堵塞或报错;然后用semop函数把信号量的值+1(也即:semval为0时可以立即通过,否则就要等待)。本步骤中的两个动作,必须通过semop的实参一次把两个动作都输入进去,而不能分别调用两次semop来实现。
(3) V操作,用semop函数设置信号值-1,注意:只有信号量值≥abs(-1)时,才能够立即减1后立即返回,否则本线程又得等待,直到信号量值≥abs(-1)。当然,因为P操作已经把信号量值+1了,所以这里信号量值肯定是≥abs(-1)。

分析:①整个程序中首次执行P操作的时候,情况是怎样的?看步骤(2),设置本线程为等待semval变为0,因为semval被初始化为0了,所以semop会立即返回或者继续执行形参指定的下一个动作:把semval+1。+1这种动作永不堵塞,于是本线程将继续向下执行开始访问共享资源。
    ②当某个线程A执行P之后,尚未V之前,又有另个线程B开始执行P了,情况是怎样的?还是看步骤(2),设置本线程B为等待semval变为0,因为线程A已经把semval设为1了,于是线程B被堵塞或报错。
    ③ 为什么步骤(2)中不允许把等待0和+1这两个动作分别用两次semop来实现?试想这样一种情形:semval初始化为0,当进程A等待0时,发现确实是0,于是继续向下执行semop的+1(进而开始访问共享资源),这时发生了进程/线程调度,切入了线程B,线程B恰好也要执行P操作,等待semval变为0,也发现确实是0,于是继续向下执行semop的+1 (进而开始访问共享资源),显然,没有达到预想的互斥的效果。semop函数提供了一种机制,把多个动作(称为动作集)通过形参一次性传入进去之后,操作系统可以保证,这些动作要么一个也不执行,要么全部被执行(除非:如果某个动作设置的条件会堵塞线程时,等堵塞解除后,后面的动作才会执行),这就杜绝了这一问题。

方法2:
步骤:
① 用semctl或者semop把信号量值初始化为1
② P操作,用semop函数设置线程等待,直到semval≥abs(-1)才解除等待(也即,semval≥1时可以立即通过,否则就要等待);
③ V操作,用semop函数设置semval+1

分析:①整个程序中首次执行P操作的时候,情况是怎样的?看步骤(2),因为semval被初始化为1了,故本次P操作并不堵塞或出错,而是把semval-1后直接返回,P操作完成后semval就变成0了;

 ②当某个线程A执行P之后,尚未V之前,又有另个线程B开始执行P了,情况是怎样的?还是看步骤(2),设置本线程B为等待等待semval≥abs(-1),因为线程A的P已经把semval设为0了,于是线程B被堵塞或报错;

 

 

 

应用情景二:例如,某个资源最多只允许5个线程同时访问

这种应用场景的一个更贴近生活的例子:某开水房的水管上(这跟水管就是个共享资源),只有5个水龙头,那么这跟水管最多只允许5个人同时打水。每来一个人打水,信号量减1,每走一个人,信号量+1,也即:只要空闲水龙头的数目(信号量)≥1,就可以放人进来打水,否则,都得排队等。

这种应用情景的处理方法,和上面提到的方法(2)是一样的,唯一的区别就是初始化时,要信号量的值初始化为n:

方法3:
步骤:
① 用semctl或者semop把信号量值初始化为5;
② P操作,用semop函数设置线程等待,直到semval≥abs(-1)才解除等待(也即,semval≥1时可以立即通过,否则就要等待);

③ V操作,用semop函数设置semval+1;

分析: 程序把信号量值初始化为5以后,同时有13个线程发起了P操作,请求访问共享资源,这时情况是怎样的?名义上是同时,实际上在该信号量的信息维护链表中,发起P操作的线程仍然是有先后的,第一名开始执行P,信号量发现自己的值是5,可以满足第一名提出的semval≥abs(-1)无需等待条件,于是第一名无需等待,P操作直接返回(<的操作返回时会把信号量值减掉),从而可以访问共享资源了;第二名线程开始执行P操作,信号量发现自己是4,也可以满足不等待的条件semval≥abs(-1)······,也即,前5名线程执行P操作,完全不用等待,都可以直接获得共享资源,而其余的13-7=7个线程执行P操作会被阻塞。前5名当中,一旦有其中一个用完了资源并释放了资源(执行V操作)之后,那么第6名线程就会解除等待,从而获得共享资源的访问权。

 

示例代码:

由以上分析可见,方法3已经把方法2的情形包含进去了,这里只给出方法1和方法3的实例代码:

运行结果分析:

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Linux中,使用信号的流程一般如下: 1. 使用semget系统调用创建或获取一个信号集,得到一个信号集标识符。 2. 使用semctl系统调用初始化或控制信号集,例如设置初始值、获取当前值等。 3. 使用semop系统调用进行信号操作,例如加锁、解锁等。 其中,关键的系统调用有: 1. semget:创建或获取一个信号集,并返回信号集标识符。 2. semctl:初始化或控制信号集,例如设置初始值、获取当前值等。 3. semop:进行信号操作,例如加锁、解锁等。 以下是一个使用信号的示例代码: ``` #include <stdio.h> #include <stdlib.h> #include <sys/sem.h> int main() { int semid; struct sembuf semop_arg; // 创建或获取信号集 semid = semget(IPC_PRIVATE, 1, IPC_CREAT|0666); if (semid == -1) { perror("semget error"); exit(EXIT_FAILURE); } // 初始化信号集 semctl(semid, 0, SETVAL, 1); // 加锁 semop_arg.sem_num = 0; semop_arg.sem_op = -1; semop_arg.sem_flg = SEM_UNDO; semop(semid, &semop_arg, 1); // 解锁 semop_arg.sem_num = 0; semop_arg.sem_op = 1; semop_arg.sem_flg = SEM_UNDO; semop(semid, &semop_arg, 1); // 删除信号集 semctl(semid, 0, IPC_RMID, 0); return 0; } ``` 在这个示例中,程序使用semget函数创建一个大小为1的信号集,并使用semctl函数信号初始值设置为1。然后,程序使用semop函数进行加锁和解锁操作。最后,程序使用semctl函数删除信号集。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值