首页 > 系统相关 >同步互斥——进程篇(一)信号量

同步互斥——进程篇(一)信号量

时间:2024-05-31 21:01:00浏览次数:26  
标签:同步 int errno 信号量 互斥 error sem include

信号量 (Semaphores)

信号量是一种用于进程间或线程间同步的机制。它可以限制多个进程或线程对共享资源的并发访问,确保资源被安全使用。信号量的核心思想是通过计数来控制访问,计数值表示当前可以访问资源的可用数量。

  • 计数器: 信号量的核心是一个整数计数器。
    • 当计数器大于0时,表示有资源可用。
    • 当计数器等于0时,表示资源不可用,需要等待。
  • 操作:
    • 等待(Wait/P): 也叫P操作,尝试减小信号量的计数器(-1)。如果计数器为0,进程或线程将被阻塞,直到计数器大于0。
    • 释放(Signal/V): 也叫V操作,增加信号量的计数器(+1)。如果有进程或线程在等待信号量,它们会被唤醒。

POSIX 信号量

POSIX信号量是基于POSIX标准实现的,可以用于进程间或线程间的同步。一般用于描述一种共享资源的状态,Linux系统把POSIX信号量分为两种:POSIX匿名信号量(和具名)。Linux系统也把POSIX信号量存储在根文件系统的/dev/shm目录下(被任意有权限的进程访问)。

以下是一些常用在进程的POSIX具名信号量函数:

函数及其用途

  • sem_open

    • 用途: 创建或打开一个命名信号量。

    • 原型: sem_t *sem_open(const char *name, int oflag);

    sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);

    • **@param **

    • name信号量名;

    • oflag用来指定打开信号量的行为(例如,是否创建它,是否非阻塞等);

    • mode指定了信号量的访问权限;

    • value 是信号量的初始值,通常用于计数信号量。

    示例:

    /*在/usr/include/x86_64-linux-gnu/bits/semaphore.h文件中的sem_t*/
    typedef union
    {
      char __size[__SIZEOF_SEM_T];
      long int __align;
    } sem_t;
    
    sem_t *sem = sem_open("/my_sem", O_CREAT | O_EXCL, 0644, 1);
    if (sem == SEM_FAILED) {
         if (errno == EEXIST) {
             sem_t *sem = sem_open("/my_sem", 0 , 0644, 1);
         } else {
             fprintf(stderr, "sem_open error,error:%d, %s\n", errno, strerror(errno));
             exit(EXIT_FAILURE);
         }
    }
    
  • sem_wait

    • 用途: 等待信号量(P操作),计数器减1。如果计数器为0,则阻塞。

    • 原型: int sem_wait(sem_t *sem);

  • sem_post

    • 用途: 释放信号量(V操作),计数器加1。如果有等待的进程或线程,则唤醒其中一个。

    • 原型: int sem_post(sem_t *sem);

  • sem_close

    • 用途: 关闭信号量的引用,但不删除信号量。

    • 原型: int sem_close(sem_t *sem);

  • sem_unlink

    • 用途: 删除信号量,释放其名字。只有当所有对信号量的引用都被关闭后,信号量才会被真正删除。

    • 原型: int sem_unlink(const char *name);

System V 信号量

System V信号量是早期UNIX系统的实现方式,主要用于进程间同步。它们提供了更复杂的功能,但使用起来也相对复杂。

函数及其用途

  • semget

    • 用途: 创建或获取一个信号量集。

    • 原型: int semget(key_t key, int nsems, int semflg);

  • semop

    • 用途: 执行一个或多个信号量操作(P操作和V操作)。

    • 原型: int semop(int semid, struct sembuf *sops, unsigned nsops);

  • semctl

    • 用途: 控制信号量集,如获取和设置信号量值,删除信号量集等。

    • 原型: int semctl(int semid, int semnum, int cmd, ...);

POSIX 信号量与 System V 信号量的区别

  • 命名机制:
    • POSIX 信号量: 支持命名信号量,允许进程间共享。
    • System V 信号量: 使用键值(key)来标识信号量集,需要通过ftok函数生成键值。
  • 用法范围:
    • POSIX 信号量: 可以用于进程间和线程间同步。
    • System V 信号量: 主要用于进程间同步。

示例:使用 POSIX 信号量

设计两个程序A和B,程序A和程序B需要创建一个共享内存,然后要求程序A把自己的PID写入到共享内存中,当程序A写入完成后,程序B从共享内存中读取程序A的PID并输出到终端,要求双方使用POSIX具名信号量实现同步。

//程序A把自己的PID写入到共享内存
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <semaphore.h>
#include <fcntl.h>
#include <unistd.h>


int shm_id;

sem_t *sem;
const char *sem_name = "/my_sem";
int oflag = O_CREAT | O_EXCL;
/* 在UNIX和类UNIX操作系统中,S_IRUSR、S_IWUSR、S_IRGRP、S_IWGRP、S_IROTH 和 S_IWOTH 是权限位宏。
 * 用于设置文件或目录的访问权限,这些宏定义在 <sys/stat.h> 头文件中。
 * S_IRUSR (Read by owner):   所有者有读权限;
 * S_IWUSR (Write by owner):  所有者有写权限;
 * S_IRGRP (Read by group):   同组用户有读权限;
 * S_IWGRP (Write by group):  同组用户有写权限;
 * S_IROTH (Read by others):  其他用户有读权限;
 * S_IWOTH (Write by others):  其他用户有写权限; */
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
unsigned int value = 0;

int main() {

    //创建共享内存段
    shm_id = shmget(ftok(".", 1), 4, IPC_CREAT | IPC_EXCL | 0644);
    if (shm_id == -1) //判断创建是否出错
    {
        if (errno == EEXIST) {
            shm_id = shmget(ftok(".", 1), 4, 0644);
        } else {
            fprintf(stderr, "shmget error,error:%d, %s\n", errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
    }

    //映射虚拟内存空间
    int *buffer = (int *) shmat(shm_id, NULL, 0);
    if (buffer == (void *) -1) //判断创建是否出错
    {
        fprintf(stderr, "shmat error,error:%d,%s\n", errno, strerror(errno));
        exit(EXIT_FAILURE);
    }

    //创建信号量
    sem = sem_open(sem_name, oflag, mode, value);
    if (sem == SEM_FAILED) {
        if (errno == EEXIST) {
            sem = sem_open(sem_name, 0, mode, value);
        } else {
            fprintf(stderr, "sem_open error,error:%d, %s\n", errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
    }

    //临界区
    *buffer = getpid();

    //V操作
    sem_post(sem);

    //关闭信号量
    if (sem_close(sem) < 0) {
        fprintf(stderr, "sem_close error,error:%d, %s\n", errno, strerror(errno));
        exit(EXIT_FAILURE);
    }
    return EXIT_SUCCESS;
}
//程序B从共享内存中读取程序A的PID并输出到终端
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <semaphore.h>
#include <fcntl.h>
#include <unistd.h>

int shm_id;

sem_t *sem;

int main() {

    //打开共享内存段
    shm_id = shmget(ftok(".", 1), 4, 0644);
    if (shm_id == -1) //判断创建是否出错
    {
            fprintf(stderr, "shmget error,error:%d, %s\n", errno, strerror(errno));
            exit(EXIT_FAILURE);
    }

    //映射虚拟内存空间
    int *buffer = (int *) shmat(shm_id, NULL, 0);
    if (buffer == (void *) -1) //判断创建是否出错
    {
        fprintf(stderr, "shmat error,error:%d,%s\n", errno, strerror(errno));
        exit(EXIT_FAILURE);
    }

    //打开信号量
    sem = sem_open("/my_sem", 0);
    if (sem == SEM_FAILED) {
            fprintf(stderr, "sem_open error,error:%d, %s\n", errno, strerror(errno));
            exit(EXIT_FAILURE);
    }

    //P操作
    sem_wait(sem);
    
    //临界区
    printf("Procedure A pid = %d\n", *buffer);

    //关闭信号量
    if (sem_close(sem) < 0) {
        fprintf(stderr, "sem_close error,error:%d, %s\n", errno, strerror(errno));
        exit(EXIT_FAILURE);
    }
    return EXIT_SUCCESS;
}

示例:使用 System V 信号量

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

int main() {
    key_t key = ftok("semfile", 1); // 生成唯一键值
    int semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666); // 创建信号量集

    // 初始化信号量值
    semctl(semid, 0, SETVAL, 1);

    struct sembuf sop;

    // P操作
    sop.sem_num = 0;
    sop.sem_op = -1;
    sop.sem_flg = 0;
    semop(semid, &sop, 1);
    
    printf("In critical section\n");
    sleep(2);  // 模拟临界区操作
    
    // V操作
    sop.sem_op = 1;
    semop(semid, &sop, 1);

    printf("Out of critical section\n");

    // 删除信号量集
    semctl(semid, 0, IPC_RMID);

    return 0;
}

标签:同步,int,errno,信号量,互斥,error,sem,include
From: https://www.cnblogs.com/Mr--Song/p/18225269

相关文章

  • 系统编程练习题----利用条件量和互斥锁,实现两个线程之间的同步与互斥
    目录题目解析代码结果展示题目解析​ 该题主要依靠条件量和互斥锁来实现线程之间的同步与互斥,分析主线程、线程A和线程B的任务如下:主线程:打开LCD屏和触摸屏的硬件文件,并分别存储两个文件的文件描述符,方便后面进行条件判断。开启线程A和线程B。定义并初始化条件量和互斥......
  • 使用条件量和互斥锁实现线程的同步以及临界资源的互斥访问
    /********************************************************************** name :* function:主线程需要创建2个子线程之后主线程终止,此时进程中有2个子线程A和B,此时进程中有一个临界资源fag,子线程A获取触摸屏坐标并判断坐标值是否在LCD屏的左上角,如果坐标范围满足......
  • 进程控制(互斥锁)
    进程控制(互斥锁)目录进程控制(互斥锁)头文件全局变量234验证头文件/********************************************************************** name :* function:主线程需要创建2个子线程之后主线程终止,此时进程中有2个子线程A和B,此时进程中有一个临界资源fag,子线程......
  • 通过互斥锁+条件量的方式实现同步与互斥
    #include<stdio.h>#include<stdlib.h>#include<pthread.h>#include<semaphore.h>#include<unistd.h>#include<string.h>#include<fcntl.h>//forO_CREATandO_EXCL#include<sys/ipc.h>#include<sys/s......
  • 向GitHub远程仓库同步文件使用经验【2】
    新手流畅一顿操作由于没搞懂CSDN的更新文档策略,只能把新写的内容作为新文章发布了。前一篇文章在这将本地仓库与远程仓库同步当本地仓库没修改,但远程仓库修改了,这时可以吧远程仓库同步到本地仓库===方法一===gitfetch //将本地仓库中的远程分支更新成了远程仓库相应......
  • PMSM永磁同步电机滑膜控制SVPWM矢量控制(Simulink仿真实现)
      ......
  • 互斥锁练习题
    练习:设计一个程序,程序中有3个线程,主线程A创建一个文本,每隔5s获取一次系统时间并写入到该文本中,另外两个线程B和C分别从文本中读取当前的时间和日期,子线程B输出系统时间"hh:mm:ss",子线程c输出系统日期"2024年05月31日”,要求使用读写锁实现互斥。提示:主线程A获取写操作的锁,另外......
  • Rsync数据同步
    目标端配置:一、安装yum-yinstallrsync二、配置文件vi/etc/rsyncd.conf用户iduid=rsync组idgid=rsync程序安全设置usechroot=no客户端连接数maxconnections=200进程号文件位置pidfile=/var/run/rsyncd.pid进程锁文件位置lockfile=/var/run/rsy......
  • c# NTP 时间同步
    using System;using System.Collections.Generic;using System.Text;using System.Net;using System.Net.Sockets;namespace SmartConstructionSite.Common.Helper{    public class NTPHelper    {        public const string WINDOWS_SE......
  • datax 从 hive 同步数据配置
    DataXHiveReader1快速介绍Hivereader插件:从Hive表读取数据2实现原理实现方式是:根据配置的QuerySql,通过将查询结果保存到一张新的临时hive表中这种方式;然后获取临时表的hdfs文件地址,然后读取文件到缓冲区,最后删除临时的表。3功能说明Hivereader插件:从Hive表读取数据......