Linux系统编程6:信号量

0. 信号量

  • 背景
#include 
#include 
#include 
int main(int argc,char * argv[]){
 
    fork();
    int i=0;
    for(;i<5;i++){
        printf("PID:%d,enter\n",getpid());
        sleep(1);// do something
        printf("PID:%d,do something\n",getpid());
        printf("PID:%d,leave\n",getpid());
    }
}

数据竞争(Data Race)

  • 来源


    Linux系统编程6:信号量_第1张图片
  • 分类

根据共享资源的数目可分为二值信号量和计数信号量两类。

No. 分类 取值 e.g.
1 二值信号量 01 指示锁
2 计数信号量 0n 停车场电子牌
Linux系统编程6:信号量_第2张图片
指示锁
停车场电子指示牌
  • 作用
    控制多进程共享资源的访问(资源有限并且不共享)

  • 本质
    任一时刻只能有一个进程访问临界区(代码),数据更新的代码。

  • 基本操作:PV

原子操作操作也被成为PV原语(P来源于Dutchproberen"测试",V来源于Dutchverhogen"增加")

信号量
  • 信号
No. 分类 取值
1 P(信号量) 0:挂起进程;>0:减1
2 V(信号量) 0:恢复进程;>0:加1

1. POSIX 信号量

  • 资料:unpv22e-ch10.1~10.13
  • 查看:man sem_overview

1.1 接口

  • 头文件:semaphore.h

  • 库:pthread

  • 分类
    信号量分为命名信号量(基于文件)与匿名信号量(基于内存)两种。

1.2 函数

1.2.1 命名信号量/基于文件

No. 操作 函数
1 创建 sem_t *sem_open(const char *name, int oflag, mode_t mode,unsigned int value)
2 删除 int sem_unlink(const char *name)
3 打开 sem_t *sem_open(const char *name, int oflag)
4 关闭 int sem_close(sem_t *sem)
5 挂出 int sem_post(sem_t *sem)
6 等待 int sem_wait(sem_t *sem)
7 尝试等待 int sem_trywait(sem_t *sem)
8 获取信号量的值 int sem_getvalue(sem_t *sem, int *sval)
1.2.1.1 创建
sem_t *sem_open(const char *name, int oflag, mode_t mode,unsigned int value)
  • 参数
No. 参数 含义
1 name 信号量IPC名字
2 oflag 标志
3 mode 权限位
4 value 信号量初始值

IPC名字有什么要求?

  • 返回值
No. 返回值 含义
1 SEM_FAILED 信号量的指针
2 SEM_FAILED 出错
  • 示例
1.2.1.2 删除
int sem_unlink(const char *name)
  • 参数
No. 参数 含义
1 name 信号量IPC名字
  • 返回值
No. 返回值 含义
1 -1 出错
2 0 成功
1.2.1.3 打开
sem_t *sem_open(const char *name, int oflag)
  • 参数
No. 参数 含义
1 name 信号量IPC名字
2 oflag 标志
  • 返回值
No. 返回值 含义
1 SEM_FAILED 信号量的指针
2 SEM_FAILED 出错
1.2.1.4 关闭
int sem_close(sem_t *sem)
  • 参数
No. 参数 含义
1 sem 信号量的指针
  • 返回值
No. 返回值 含义
1 -1 出错
2 0 成功
1.2.1.5 挂出
int sem_post(sem_t *sem)
  • 参数
No. 参数 含义
1 sem 信号量的指针
  • 返回值
No. 返回值 含义
1 -1 出错
2 0 成功
1.2.1.6 等待
int sem_wait(sem_t *sem)
  • 参数
No. 参数 含义
1 sem 信号量的指针
  • 返回值
No. 返回值 含义
1 -1 出错
2 0 成功
1.2.1.7 尝试等待
int sem_trywait(sem_t *sem)
  • 参数
No. 参数 含义
1 sem 信号量的指针
  • 返回值
No. 返回值 含义
1 -1 出错
2 0 成功
1.2.1.8 获取信号量的值
int sem_getvalue(sem_t *sem, int *sval)
  • 参数
No. 参数 含义
1 sem 信号量的指针
2 sval 信号量的值
  • 返回值
No. 返回值 含义
1 -1 出错
2 0 成功
数据竞争解决方法
  • 同步操作
    上面竞争可以使用信号量解决。
#include 
#include 
#include 
#include 
#include 
 
int main(int argc,char * argv[]){
 
    sem_t* sem = sem_open("/sem.tmp",O_CREAT|O_RDWR,0644,1);
    fork();
    int i=0;
    for(;i<5;i++){
        sleep(1);
        sem_wait(sem);
        printf("PID:%d,enter\n",getpid());
        printf("PID:%d,do something\n",getpid());
        printf("PID:%d,leave\n",getpid());
        sem_post(sem);
    }
}
  • 同步访问共享内存
#include 
#include  
#include 
#include 
#include 
using namespace std;

enum ARGS{
    CMD,SEM_NAME,SHM_NAME
};
int main(int argc,char* argv[]){
   if(3!=argc){
       cout << "Usage:" << argv[CMD] << " sem_name shm_name" << endl;
       return EXIT_FAILURE;
   }
   // 创建(如果不存在)打开共享内存
   int shm = shm_open(argv[SHM_NAME],O_CREAT|O_RDWR,0644);
   if(-1 == shm){
        perror("shm_open error");
        return EXIT_FAILURE;
   }
   ftruncate(shm,sizeof(int));

   // 建立映射
   void* p = mmap(NULL,sizeof(int),PROT_WRITE|PROT_READ,MAP_SHARED,shm,0);
   if(MAP_FAILED == p){
        perror("mmap error");
        return EXIT_FAILURE;
   }
   
   int* n = reinterpret_cast(p);
   *n = 0;
   
   // 创建(如果不存在)打开二值信号量
   sem_t* sem = sem_open(argv[SEM_NAME],O_CREAT|O_RDWR,0644,1);
   fork();
   for(int i=0;i<5;++i){
      usleep(10000);
      sem_wait(sem);
      ++*n;
      cout << getpid() << ":" << *n << endl;
      sem_post(sem);
   }
   sem_close(sem);
   
   // 解除映射
   munmap(p,sizeof(int));
   return EXIT_SUCCESS; 
}

可以通过注释fork(),重新编译程序后同时执行两次程序演示两个非亲缘进程的同步。

1.2.2 匿名信号量/基于内存

No. 操作 函数
1 初始化 int sem_init (sem_t *sem , int pshared, unsigned int value)
2 销毁 int sem_destroy(sem_t *sem)
3 挂出 int sem_post(sem_t *sem)
4 等待 int sem_wait(sem_t *sem)
5 尝试等待 int sem_trywait(sem_t *sem)
6 获取信号量的值 int sem_getvalue(sem_t *sem, int *sval)

其中3~4操作与命名信号量相同。

1.2.2.1 初始化
int sem_init (sem_t *sem , int pshared, unsigned int value)
  • 参数
No. 参数 含义
1 sem 信号量的指针
2 pshared 共享方式。0:线程间共享;1:进程间共享,需要共享内存
3 value 信号量初始值
  • 返回值
No. 返回值 含义
1 -1 出错
2 0 成功
1.2.2.2 销毁
int sem_destroy(sem_t *sem)
  • 参数
No. 参数 含义
1 sem 信号量的指针
  • 返回值
No. 返回值 含义
1 -1 出错
2 0 成功
数据竞争解决方法
  • 操作同步
#include 
#include 
#include 
#include 
#include 
 
int main(int argc,char * argv[]){
 
    sem_t* sem = sem_open("/sem.tmp",O_CREAT|O_RDWR,0644,0);
    fork();
    int i=0;
    for(;i<5;i++){
        sleep(1);
        sem_wait(sem);
        printf("PID:%d,enter\n",getpid());
        printf("PID:%d,do something\n",getpid());
        printf("PID:%d,leave\n",getpid());
        sem_post(sem);
    }
}
  • 同步访问共享内存
#include 
#include  
#include 
#include 
#include 
using namespace std;
struct Data{
    sem_t sem;
    int data;
};

int main(int argc,char* argv[]){
   // 匿名共享内存
   void* p = mmap(NULL,sizeof(Data),PROT_WRITE|PROT_READ,MAP_SHARED|MAP_ANON,-1,0);
   if(MAP_FAILED == p){
        perror("mmap error");
        return EXIT_FAILURE;
   }
   Data* d = reinterpret_cast(p);
   d->data = 0;
   // 创建二值信号量
   sem_init(&d->sem,1,1);
   fork();
   for(int i=0;i<5;++i){
      usleep(10000);
      sem_wait(&d->sem);
      ++(d->data);
      cout << getpid() << ":" << d->data << endl;
      sem_post(&d->sem);
   }
   sem_destroy(&d->sem);
   munmap(p,sizeof(Data));
   return EXIT_SUCCESS; 
}

2. 小结

命名信号量与匿名信号量

3. 计数信号量

模拟一个停车场的电子显示牌

  • park.cpp
#include 
#include 
#include 
#include 
#include 
 
int main(int argc,char * argv[]){
    sem_t* sem = sem_open(argv[1],O_CREAT,0644,atoi(argv[2]));
}
  • car.cpp
#include 
#include 
#include 
#include 
#include 
#include 
 
void handler(int sig){
    printf("PID:%d,prepare to leave\n",getpid());
}
 
int main(int argc,char * argv[]){
    signal(SIGINT,handler);
    sem_t* sem = sem_open(argv[1],O_RDWR);
    sem_wait(sem);
    int val;
    sem_getvalue(sem,&val);
    printf("PID:%d,enter. park %d\n",getpid(),val);
    printf("PID:%d,do something\n",getpid());
    pause();
    sem_post(sem);
    sem_getvalue(sem,&val);
    printf("PID:%d,leave. park %d\n",getpid(),val);
}
  • car_try.cpp
#include 
#include 
#include 
#include 
#include 
#include 
 
void handler(int sig){
    printf("PID:%d,prepare to leave\n",getpid());
}
 
int main(int argc,char * argv[]){
 
    signal(SIGINT,handler);
 
    sem_t* sem = sem_open(argv[1],O_RDWR);
    if(-1 == sem_trywait(sem)){
        perror("no park");
        return 1;
    }
    int val;
    sem_getvalue(sem,&val);
    printf("PID:%d,enter. park %d\n",getpid(),val);
    printf("PID:%d,do something\n",getpid());
    pause();
    sem_post(sem);
    sem_getvalue(sem,&val);
    printf("PID:%d,leave. park %d\n",getpid(),val);
}

5. 练习

把信号量封装成一个信号量类

你可能感兴趣的:(Linux系统编程6:信号量)