Linux 信号量

信号量

    • 信号量
    • 信号量的定义
    • 信号量理论例子
    • Linux信号量机制
    • 使用信号量

信号量

信号量:用于管理对资源的访问。
(1) 当我们编写的程序使用了线程时,不管它是运行在多用户系统上、多进程系统上,还是运行在多用户多进程系统上,我们通常会发现,程序中存在着一部分临 界代码,我们需要确保只有一个进程 (或一个执行线程) 可以进入这个临界代码并拥有对资源独占式的访问权。

(2)信号量有着复杂的编程接口,但幸运的是,我们可以很轻松地为自己提供一个更简单的接口,它足够应付大多数信号量编程的问题。

(3)为了防止出现因多个程序同时访问-一个共享资源而引发的问题,我们需要有一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。
(4)要想编写通用的代码,以确保程序对某个特定的资源具有独占式的访问权是非常困难的。虽然有一个名为Dekker算法的解决方法,但这个算法依赖于“忙等待”或“自旋锁”。也就是说,一个进程要持续不断地运行以等待某个内存位置被改变。在像Linux这样的多任务环境中,人们并不愿意使用这种浪费CPU资源的处理方法。但如果硬件支持独占式访问(一般是通过特定的CPU指令的形式),那么情况就变得简单多了。一个硬件支持的例子就是,用一条指令以原子方式访问并增加寄存器的值,在这个读取/增加/写入操作执行的过程中不会有其他指令(甚至一个中断)发生。
(5)一种可能的解决方法是,使用带O_ EXCL标志的open函数来创建锁文件,它提供了原子化的文件创建方法。它允许一个进程通过获取一个令牌(即新创建的文件)来取得成功。这个方法比较适合于处理简单的问题,但对于更复杂的例子,它就显得比较杂乱且缺乏效率。
(6)荷兰计算机科学家Edsger Dijkstra提出的信号量概念是在并发编程领域迈出的重要一步。 信号量是一个特殊的变量,它只取正整数值,并且程序对其访问都是原子操作。

信号量的一个更正式的定义是:它是一个特殊变量,只允许对它进行等待(wait) 和发送信号(signal)这两种操作。因为在Linux编程中,“等待”和“发送信号”都已具有特殊的含义,所以我们将用原先定义的符号来表示这两种操作。

  • P (信号量变量):用于等待。
  • V (信号量变量):用于发送信号。
    这两个字母分别来自于荷兰语单词passeren(传递,就好像位于进入临界区域之前的检查点)和vrijgeven (给予或释放,就好像放弃对临界区域的控制权)。在与信号量关联的内容中,你可能还会看到术语“开”(up)和“关”(down),它们取自开、关信号标志的用法。

信号量的定义

最简单的信号量是只能取值0和1的变量,即二进制信号量。这也是信号量最常见的一一种形式。可以取多个正整数值的信号量被称为通用信号量。
PV操作的定义非常简单。假设有一个信号量变量sv,则这两个操作的定义如图:
在这里插入图片描述

还可以这样看信号量:当临界区域可用时,信号量变量sv的值是true,然后P(sv)操作将它减1使它变为false以表示临界区域正在被使用;当进程离开临界区域时,使用V(sv) 操作将它加1,使临界区域再次变为可用。注意,只用一个普通变量进行类似的加减法是不行的,因为在C、C++、C#或几乎任何一个传统的编程语言中,都没有一个原子操作可以满足检测变量是否为true,如果是再将该变量设置为false的需要。这也是信号量操作如此特殊的原因。

信号量理论例子

我们用一个简单的理论性的例子来说明其工作原理。假设有两个进程proc1和proc2,这两个进程都需要在其执行过程中的某–时刻对–个数据库进行独占式的访问。我们定义一一个二进制信号量sv,该变量的初始值为1,两个进程都可以访问它。要想对代码中的临界区域进行访问,这两个进程都需要执行相同的处理步骤,事实上,这两个进程可以只是同一个程序的两个不同执行实例。两个进程共享信号量变量sv。一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区域。而第二个进程将被阻止进入临界区域,因为当它试图执行P(sv)操作时,它会被挂起以等待第一一个进程离开临界区域并执行V(sv)操作释放信号量。
需要的伪代码对两个进程都是相同的,如下所示:

semaphore sv = 1;
loop forever {
P(sv) ;
critical code section;
V(sv) ;
noncritical code section;
}

这段代码相当简单,这是因为PV操作的功能非常强大。如图显示了Pv操作是如何把守代码中的临界区域的。

Linux 信号量_第1张图片

Linux信号量机制

. 头文件

 #include 
 #include 
 #include 
 #include 
  • int semget(key_t key, int nsems, int semflg);
semget()创建或者获取已存在的信号量
semget()成功返回信号量的 ID, 失败返回-1
key:两个进程使用相同的 key 值,就可以使用同一个信号量
nsems:内核维护的是一个信号量集,在新建信号量时,其指定信号量集中信号量的个数
semflg 可选: IPC_CREAT IPC_EXCL
  • *int semop( int semid, struct sembuf sops, unsigned nsops);
semop()对信号量进行改变,做 P 操作或者 V 操作
semop()成功返回 0,失败返回-1
struct sembuf
{
unsigned short sem_num; //指定信号量集中的信号量下标
short sem_op; //其值为-1,代表 P 操作,其值为 1,代表 V 操作
short sem_flg; //SEM_UNDO
};
  • int semctl( int semid, int semnum, int cmd, …);
semctl()控制信号量
semctl()成功返回 0,失败返回-1
cmd 选项: SETVAL IPC_RMID
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *_buf;
};
  • int sem_init(sem_t *sem, int pshared, unsigned int value);
  • int sem_wait(sem_t *sem);
  • int sem_post(sem_t *sem);
  • int sem_destroy(sem_t *sem);

使用信号量

两线程完成输入

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

 char buff[128] = {0};

 sem_t sem1;
 sem_t sem2;

 void* PthreadFun( void *arg)
 {
 int fd = open("a.txt", O_RDWR | O_CREAT, 0664);
 assert(fd != -1);

 //函数线程完成将用户输入的数据存储到文件中
 while(1)
 {
 sem_wait(&sem2);

 if(strncmp(buff, "end", 3) == 0)
 {
 break;
 }

 write(fd, buff, strlen(buff));

 memset(buff, 0, 128);

 sem_post(&sem1);
 }

 sem_destroy(&sem1);
 sem_destroy(&sem2);
 }

 int main()
 {
 sem_init(&sem1, 0, 1);
 sem_init(&sem2, 0, 0);

 pthread_t id;
 int res = pthread_create(&id, NULL, PthreadFun, NULL);
 assert(res == 0);

 //主线程完成获取用户数据的数据,并存储在全局数组 buff 中
 while(1)
 {
 sem_wait(&sem1);

 printf("please input data: ");
 fflush(stdout);
 fgets(buff, 128, stdin);
 buff[strlen(buff) - 1] = 0;

 sem_post(&sem2);

 if(strncmp(buff, "end", 3) == 0)
 {
 break;
 }
 }

 pthread_exit(NULL);
 }

Linux 信号量_第2张图片

你可能感兴趣的:(Linux,linux,运维,服务器)