线程同步3 ------ 信号量实现进程或者线程之间的同步

基本概念

      首先要注意,信号量和信号是完全两码事。信号量是一个计数器,常用于处理进程或线程的同步问题,特别是对临界资源访问的同步。临界资源可以简单地理解为在某一时刻只能由一个进程或线程进行操作的资源。通常,程序对共享资源的访问的代码只是很短的一段,但就是这一段代码引发了进程之间的竞态条件。这段代码称为关键代码段,或者临界区。对进程同步,也就是确保任一时刻只有一个进程能进入关键代码段。

      信号量的值与相应资源的使用情况有关,当它的值大于0时,表示当前可用资源的数量,当它的值小于0时,其绝对值表示等待使用该资源的进程个数。信号量的值仅能由pv操作来改变。在Linux下,pv操作通过调用函数semop实现。

      信号量是一种特殊的变量,它只支持2种操作:P操作(进入临界区)和V操作(退出临界区)。假设有信号量SV,则对它的P、V操作含义如下:

  • P(SV),如果SV的值大于0,意味着可以进入临界区,就将它减1;如果SV的值为0,意味着别的进程正在访问临界区,则挂起当前进程的执行;;
  • V(SV),当前进程退出临界区时,如果有其他进程因为等待SV而挂起,则唤醒之;如果没有,则将SV加1,之后再退出临界区。



与信号量相关的数据类型和函数

      semid:信号集的标识符

      struct semid_ds:信号集数据结构

      struct sembuf:对应一个特定信号的操作

      union semun:共用体变量,用以标示特定的操作

      semget:创建或打开信号集

      sem_p:信号集的p操作函数

      sem_v:信号集的v操作函数

      semop:pv操作通过调用函数semop实现

      semctl:信号集的控制函数,比如删除信号集,对信号集的数据结构进行设置,获取信号集中信号值等。


semget系统调用

      此系统调用创建一个新的信号量集,或者获取一个已经存在的信号量集。

#include <sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);
      key是一个键值,用以标识一个全局唯一的信号集,要通过信号量通信的进程需要使用相同的键值来创建/获取该信号量。

      num_sems指定要创建/获取的信号量集中信号量的数目。如果是创建信号量,则该值必须被指定。如果是获取已经存在的信号量,则可以将其设置为0。

      sem_flags指定一组标志。
      semget调用成功会返回一个正整数值,它是信号量集的标识符;失败时返回-1。


semop系统调用

      此系统调用改变信号量的值,即执行P、V操作。

#include <sys/sem.h>
int semop(int sem_id, struct sembuf *sem_ops,size_t num_sem_ops);
      sem_id就是上述semget调用返回的信号量集标识符。

      sem_ops指向一个sembuf结构体类型的数组。

semctl系统调用

      此系统调用允许调用者对信号量进行直接控制。

#include <sys/sem.h>
int semctl(int sem_id, int sem_num, int command, ...);
      sem_id信号量集标识符。

      sem_num指定被操作的信号量在信号量集中的编号。

      command指定要执行的命令。

//在父、子进程之间使用信号量来进行同步

#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

union semun{
    int val;						//用于SETVAL命令,也即本例中的命令
    struct semid_ds *buf;			//用于IPC_STAT和IPC_SET命令
    unsigned short int *array;		//用于GETALL和SETALL命令
    struct seminfo *__buf;			//用于IPC_INFO命令
};

//op为-1时执行p操作,op为1时执行v操作
void pv(int sem_id,int op)
{
    struct sembuf sem_b;
    sem_b.sem_num=0;			//信号集中第0个信号(也就是信号集中的第一个信号)
    sem_b.sem_op=op;			//指定操作类型,可以是1,0,-1。
    sem_b.sem_flg=SEM_UNDO;		//SEM_UNDO含义是,当进程退出时取消正在进行的semop操作。
    semop(sem_id,&sem_b,1);
}

int main(int argc,char *argv[])
{
	//IPC_PRIVATE是个特殊的键值(0)。这个名称有点误导(由于历史原因),并不是“私有的”,应该称为IPC_NEW。
    int sem_id=semget(IPC_PRIVATE,1,0666);

    union semun sem_un;
    sem_un.val=1;
	//将信号量的semval值设置为semun.val,同时内核数据中的semid_ds.sem_ctime被更新
    //SETVAL操作的是单个信号量,是由标识符sem_id指定的信号量集中的第sem_num个信号量(本例即为第0个信号量)
	semctl(sem_id,0,SETVAL,sem_un);

    pid_t id=fork();
    if(id<0){
        return 1;
    }else if(id==0){
        printf("Child try to get binary sem\n");
        pv(sem_id,-1);
        printf("Child get the sem and would release it after 5 seconds\n");
        sleep(5);
        pv(sem_id,1);
        exit(0);
    }else{
        printf("Parent try to get binary sem\n");
        pv(sem_id,-1);
        printf("Parent get the sem and would release it after 5 seconds\n");
        sleep(5);
        pv(sem_id,1);
    }

    waitpid(id,NULL,0);
    semctl(sem_id,0,IPC_RMID,sem_un);
    return 0;
}

运行结果:


展开阅读全文

没有更多推荐了,返回首页