【Linux】线程同步之信号量(Semaphore)

概述

信号量概念

信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。

信号量分类

信号量分为有名信号量和内存信号量。

有名信号量通常用于进程之间的信号量同步,而无名信号量一般用于线程之间的信号量同步

函数定义

头文件

#include <semaphore.h>

有名信号量

Posix提供了基于名称的信号量接口,这些信号量由一个name参数标识,它通常指代文件系统中的某个文件,其接口定义如下:

sem_open

  • 打开(或创建)有名信号量
sem_t  *sem_open(const char *name, int oflag,..,/*mode_t mode,unsigned int value*/)

参数说明:

  • name:信号量的外部名字;
  • oflag:选择创建或打开一个现有的信号量;
  • mode:权限位;
  • value :信号量初始值;

返回值

  • 成功:指向信号量的指针
  • 失败:SEM_FAILED

对于oflag的说明

  • oflag参数能是0O_CREAT(创建一个信号量)或O_CREAT|O_EXCL(如果没有指定的信号量就创建)。
  • 如果指定了O_CREAT,那么第三个和第四个参数是需要的;
    • 其中mode参数指定权限位;value参数;
    • 指定信号量的初始值,通常用来指定共享资源的数量。该初始不能超过SEM_VALUE_MAX,这个常值必须低于为32767;
  • 如果指定了O_CREAT(而没有指定O_EXCL),那么只有所需的信号量尚未存在时才初始化他。
    • 所需信号量已存在条件下指定O_CREAT不是个错误。该标志的意思仅仅是“如果所需信号量尚未存在,那就创建并初始化他”;
    • 不过所需信号量等已存在条件下指定O_CREAT|O_EXCL却是个错误。
  • sem_open返回指向sem_t信号量的指针,该结构里记录着当前共享资源的数目。

sem_close

  • 关闭有名信号量
int sem_close(sem_t *sem);

说明:

  • 一个进程终止时,内核还对其上仍然打开着的所有有名信号量自动执行这样的信号量关闭操作。不论该进程是自愿终止的还是非自愿终止的,这种自动关闭都会发生。
  • 但应注意的是关闭一个信号量并没有将他从系统中删除。这就是说,Posix有名信号量至少是随内核持续的:即使当前没有进程打开着某个信号量,他的值仍然保持。

sem_unlink

  • 从系统中删除信号量
int sem_unlink(count char *name);

返回值

  • 成功:返回0;
  • 失败:返回-1

说明:

  • 每个信号量有一个引用计数器记录当前的打开次数,sem_unlink必须等待这个数为0时才能把name所指的信号量从文件系统中删除。也就是要等待最后一个sem_close发生。

内存信号量

Posix提供了基于内存的信号量接口,它们由应用程序分配信号量的内存空间,然后由系统初始化它们的值

sem_init

  • 初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);

参数说明:

  • sem :信号量地址。
  • pshared :0 – 线程同步 1 – 进程同步。
  • value :最多有几个 线程/进程 操作共享数据。

返回值

  • 成功:返回 0;
  • 失败:返回-1,并设置错误号;

sem_open和sem_init的区别:

  • 创建有名信号量必须指定一个与信号量相关链的文件名称,这个name通常是文件系统中的某个文件,基于内存的信号量不需要指定名称;
  • 有名信号量sem 是由sem_open分配内存并初始化成value值,基于内存的信号量是由应用程序分配内存,有sem_init初始化成为value值。如果shared为1,则分配的信号量应该在共享内存中;
  • sem_open不需要类似shared的参数,因为有名信号量总是可以在不同进程间共享的,而基于内存的信号量通过shared参数来决定是进程内还是进程间共享,并且必须指定相应的内存;
  • 基于内存的信号量不使用任何类似于O_CREAT标志的东西,也就是说,sem_init总是初始化信号量的值,因此,对于一个给定的信号量,我们必须小心保证只调用sem_init一次,对于一个已经初始化过的信号量调用sem_init,结果是未定义的;
  • 内存信号量通过sem_destroy删除信号量,有名信号量通过sem_unlink删除;

sem_destroy

  • 销毁信号量
int sem_destroy(sem_t *sem);

通用接口

sem_wait

  • 监听信号量
int sem_wait(sem_t *sem);
/* 获取信号量后,该接口返回,否则接口一直阻塞 */
 int sem_trywait(sem_t *sem);
/* 尝试获取信号量,无论是否获取成功,该接口都立刻返回*/
int sem_timedwait (sem_t *__restrict __sem,  const struct timespec *__restrict __abstime);
/* 在指定时间内尝试获取信号量,无论是否获取成功,超时后接口返回该接口都立刻返回*/

函数返回值

  • 成功:返回 0;
  • 失败:返回-1,并设置错误号;

说明:

  • 我们能用sem_wait来申请共享资源;
  • sem_wait函数能测试所指定信号量的值,如果该值大于0,那就将信号量减1并即时返回。共享资源申请成功。如果该值等于0,调用线程就被进入睡眠状态,直到该值变为大于0,这时再将他减1,函数随后返回。
  • sem_wait操作必须是原子的。
  • sem_wait和sem_trywait的差别是:当所指定信号量的值已是0时,后者并不将调用线程投入睡眠。相反,他返回一个EAGAIN错误。

sem_post

  • 释放信号量
int sem_post(sem_t *sem); 

说明:

当一个线程使用完某个信号量时,他应该调用sem_post来告诉系统申请的资源已用完。本函数和sem_wait函数的功能正好相反,他把所指定的信号量的值加1,然后唤醒正在等待该信号量值变为正数的任意线程。

sem_getvalue

  • 获取信号量的值
int sem_getvalue (sem_t *__restrict __sem, int *__restrict __sval)

说明:

该函数返回当前可用信号量的值,通过sval输出参数返回。

代码示例

内存信号量

#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>

#define MAXNUM 2
sem_t semPtr;
pthread_t a_thread, b_thread, c_thread;
int g_phreadNum = 1;

void *func1(void *arg) {
    sem_wait(&semPtr);
    printf("a_thread get a semaphore \n");
    sleep(5);
    sem_post(&semPtr);
    printf("a_thread release semaphore \n");
    pthread_join(a_thread, NULL);
}

void *func2(void *arg) {
    sem_wait(&semPtr);
    printf("b_thread get a semaphore \n");
    sleep(5);
    sem_post(&semPtr);
    printf("b_thread release semaphore \n");
    pthread_join(b_thread, NULL);
}

void *func3(void *arg) {
    sem_wait(&semPtr);
    printf("c_thread get a semaphore \n");
    sleep(5);
    sem_post(&semPtr);
    printf("c_thread release semaphore \n");
    pthread_join(c_thread, NULL);
}

int main() {
    int taskNum;

    // 创建2个信号量
    sem_init(&semPtr, 0, MAXNUM);
    //线程1获取1个信号量,5秒后释放
    pthread_create(&a_thread, NULL, func1, NULL);
    //线程2获取1个信号量,5秒后释放
    pthread_create(&b_thread, NULL, func2, NULL);

    sleep(1);
    //线程3获取信号量,只有线程1或者线程2释放后,才能获取到
    pthread_create(&c_thread, NULL, func3, NULL);
    sleep(10);
    //销毁信号量
    sem_destroy(&semPtr);
    return 0;
}

有名信号量

#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv) {
    sem_t *sem;

    if (argc != 2) {
        printf("please input a file name !\n");
        exit(1);
    }

    sem = sem_open(argv[1], O_CREAT, 0644, 1);
    /* 信号量使用请参考无名信号量种的使用方式 */
    sleep(1);
    sem_close(sem);
    sem_unlink(argv[1]);
    exit(0);
}

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux线程同步的方式有很多,以下是其中一些常见的: 1. 互斥锁(Mutex):用于保护共享资源,只允许一个线程访问共享资源。当一个线程获取到互斥锁后,其他线程就必须等待这个线程释放锁后才能获取锁。 2. 读写锁(Reader-Writer Lock):在读多写少的情况下,使用读写锁可以提高并发性能。读写锁允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。 3. 条件变量(Condition Variable):用于等待某个条件满足时才执行。当一个线程等待某个条件变量时,它会被阻塞,直到另外一个线程发出信号,通知条件已经满足,该线程才会继续执行。 4. 信号量Semaphore):用于控制同时访问共享资源的线程数量。信号量可以是计数信号量或二进制信号量。计数信号量可以用来控制多个线程同时访问一个资源的数量,而二进制信号量只允许一个线程访问资源。 5. 屏障(Barrier):用于让多个线程在某个点上同步执行。当多个线程执行到某个点时,它们会被阻塞,直到所有线程都到达该点,才会继续执行。 这些同步机制可以根据具体的场景选择使用。在实际开发中,需要考虑多线程的安全性、性能等因素,选择合适的同步机制来实现线程同步。 ### 回答2: 在Linux中,线程同步是指多个线程之间的操作需要协调,以确保它们在执行任务时能够按照预期的顺序进行。 线程同步的目的是确保多个线程共享的资源(如共享内存、文件、网络连接等)能够被有序地访问和操作,避免出现竞态条件和资源争夺等问题,确保程序的正确性和性能。 常见的线程同步机制包括互斥锁、条件变量、读写锁、信号量等。 互斥锁是最基本的一种线程同步机制,它可以确保在任何时候只有一个线程可以访问共享资源。当某个线程获取了互斥锁之后,其他线程必须等待该线程释放锁后才能继续执行。互斥锁通过使用标志位和原子操作来确保线程的互斥性。 条件变量是一种线程同步机制,它可以使线程在满足某些条件之前一直等待,从而避免忙等待和浪费资源。条件变量常与互斥锁一起使用,当共享资源不满足条件时,线程可以使用条件变量进入等待状态,直到该条件被满足,另一个线程发出信号来唤醒等待线程。 读写锁是一种用于多线程读写共享资源的机制,它允许多个线程同时进行读操作,但只允许一个线程进行写操作。读写锁可以提高程序的并发性能,但需要注意避免读-写之间的竞争条件。 信号量是一种基于计数器的线程同步机制,它可以控制共享资源的访问数量和顺序。信号量可以实现互斥锁、条件变量等多种功能,是一种比较通用的线程同步机制。 除了上述机制,Linux中还有其他一些线程同步工具和算法,如屏障、自旋锁、分段锁、标记等。不同的线程同步机制和算法适用于不同的场景和需求,需要根据具体情况进行选择和使用。 ### 回答3: 在Linux中,由于多线程同时访问共享资源可能导致竞争条件的出现,因此需要使用线程同步技术来避免这种情况。除了使用互斥锁和条件变量来实现线程同步之外,也可以使用Linux提供的信号量机制。 信号量是一个整数值,用于控制对共享资源的访问。它包括两个主要的操作:PV操作和初始化操作。PV操作分为两种:P操作(等待操作)和V操作(释放操作)。一个线程在访问共享资源之前,必须执行P操作,如果信号量的值为0,则该线程将被阻塞。当线程使用完共享资源后,必须执行V操作来释放信号量,并唤醒其他等待访问共享资源的线程。 在Linux中使用信号量需要包含头文件<sys/sem.h>,并使用semget函数创建一个新的信号量集。接着,使用semctl函数可对信号量进行初始化或者删除操作。使用semop函数可进行PV操作。 与互斥锁和条件变量相比,信号量机制的优点是可以在不同进程间进行线程同步,而且可以实现多个线程同时访问共享资源的问题。但是,使用信号量需要特别小心,因为它比互斥锁和条件变量更难调试,如果使用不当会导致死锁等问题。 总之,Linux提供了多种线程同步机制,开发人员需要根据实际需求选择合适的机制来避免竞争条件的问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值