• 互斥和信号量


    转:http://blog.csdn.net/tietao/article/details/7367827 http://blog.csdn.net/yusiguyuan/article/details/14110437

    总结:

    互斥用于线程互斥,可以理解为信号量的特例。
    信号量可用于线程、进程互斥和同步。

    “信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在semtake的时候,就阻塞在 哪里)。而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这 个资源。比如对全局变量的访问,有时要加锁,操作完了,在解锁。有的时候锁和信号量会同时使用的”
    也就是说,信号量不一定是锁定某一个资源,而是流程上的概念,比如:有A,B两个线程,B线程要等A线程完成某一任务以后再进行自己下面的步骤,这个任务 并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进 行操作。在有些情况下两者可以互换。

    两者之间的区别:

    作用域
    信号量: 进程间或线程间(linux仅线程间的无名信号量pthread semaphore)
    互斥锁: 线程间

    上锁时 
    信号量: 只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减一。若value值不大于0,则sem_wait使得线程阻塞,直到sem_post释放后value值加一,但是sem_wait返回之前还是会将此value值减一
    互斥锁: 只要被锁住,其他任何线程都不可以访问被保护的资源

    以下是信号灯(量)的一些概念:

    信号灯与互斥锁和条件变量的主要不同在于”灯”的概念,灯亮则意味着资源可用,灯灭则意味着不可用。如果说后两中同步方式侧重于”等待”操作,即资 源不可用的话,信号灯机制则侧重于点灯,即告知资源可用;
    没有等待线程的解锁或激发条件都是没有意义的,而没有等待灯亮的线程的点灯操作则有效,且能保持 灯亮状态。当然,这样的操作原语也意味着更多的开销。

    信号灯的应用除了灯亮/灯灭这种二元灯以外,也可以采用大于1的灯数,以表示资源数大于1,这时可以称之为多元灯。

    1. 创建和 注销

    POSIX信号灯标准定义了有名信号灯和无名信号灯两种,但LinuxThreads的实现仅有无名灯,同时有名灯除了总是可用于多进程之间以外,在使用上与无名灯并没有很大的区别,因此下面仅就无名灯进行讨论。

    int sem_init(sem_t *sem, int pshared, unsigned int value)
    这是创建信号灯的API,其中value为信号灯的初值,pshared表示是否为多进程共享而不仅仅是用于一个进程。LinuxThreads没有实现 多进程共享信号灯,因此所有非0值的pshared输入都将使sem_init()返回-1,且置errno为ENOSYS。初始化好的信号灯由sem变 量表征,用于以下点灯、灭灯操作。

    int sem_destroy(sem_t * sem)
    被注销的信号灯sem要求已没有线程在等待该信号灯,否则返回-1,且置errno为EBUSY。除此之外,LinuxThreads的信号灯 注销函数不做其他动作。
    sem_destroy destroys a semaphore object, freeing the resources it  might  hold.  No  threads  should  be  waiting  on  the
           semaphore  at  the  time  sem_destroy  is  called.  In  the  LinuxThreads implementation, no resources are associated with
           semaphore objects, thus sem_destroy actually does nothing except checking that no thread is waiting on the semaphore.


    2. 点灯和灭灯

    int sem_post(sem_t * sem)

    点灯操作将信号灯值原子地加1,表示增加一个可访问的资源。

    int sem_wait(sem_t * sem)
    int sem_trywait(sem_t * sem)

    sem_wait()为等待灯亮操作,等待灯亮(信号灯值大于0),然后将信号灯原子地减1,并返回。sem_trywait()为sem_wait()的非阻塞版,如果信号灯计数大于0,则原子地减1并返回0,否则立即返回-1,errno置为EAGAIN。

    3. 获取灯值

    int sem_getvalue(sem_t * sem, int * sval)

    读取sem中的灯计数,存于*sval中,并返回0。

    4. 其他

    sem_wait()被实现为取消点。(取消点事什么意思???)
    sem_wait is a cancellation point.
    取消点的含义:
    当用pthread_cancel()一个线程时,这个要求会被pending起来,当被cancel的线程走到下一个cancellation point时,线程才会被真正cancel掉。
    而且在支持原子”比较且交换CAS”指令的体系结构上,sem_post()是唯一能用于异步信号处理函数的POSIX异步信号 安全的API。

    On processors supporting atomic compare-and-swap (Intel 486, Pentium and later, Alpha, PowerPC, MIPS  II,  Motorola  68k),
           the  sem_post function is async-signal safe and can therefore be called from signal handlers. This is the only thread syn-
           chronization function provided by POSIX threads that is async-signal safe.

           On the Intel 386 and the Sparc, the current LinuxThreads implementation of sem_post is not async-signal safe  by  lack  of
           the required atomic operations.

    互斥量(Mutex)

    互斥量表现互斥现象的数据结构,也被当作二元信号灯。一个互斥基本上是一个多任务敏感的二元信号,它能用作同步多任务的行为,它常用作保护从中断来的临界段代码并且在共享同步使用的资源。

    clip_image001

    Mutex本质上说就是一把锁,提供对资源的独占访问,所以Mutex主要的作用是用于互斥。Mutex对象的值,只有0和1两个值。这两个值也分别代表了Mutex的两种状态。值为0, 表示锁定状态,当前对象被锁定,用户进程/线程如果试图Lock临界资源,则进入排队等待;值为1,表示空闲状态,当前对象为空闲,用户进程/线程可以Lock临界资源,之后Mutex值减1变为0。

    Mutex可以被抽象为四个操作:

    - 创建 Create

    - 加锁 Lock

    - 解锁 Unlock

    - 销毁 Destroy

    Mutex被创建时可以有初始值,表示Mutex被创建后,是锁定状态还是空闲状态。在同一个线程中,为了防止死锁,系统不允许连续两次对Mutex加锁(系统一般会在第二次调用立刻返回)。也就是说,加锁和解锁这两个对应的操作,需要在同一个线程中完成。

    不同操作系统中提供的Mutex函数:

    动作系统

    Win32

    Linyx

    Solaris

    创建

    CreateMutex

    pthread_mutex_init

    mutex_init

    加锁

    WaitForSingleObject

    pthread_mutex_lock

    mutex_lock

    解锁

    ReleaseMutex

    pthread_mutex_unlock

    mutex_unlock

    销毁

    CloseHandle

    pthread_mutex_destroy

    mutex_destroy

    信号量

    信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施, 它负责协调各个线程, 以保证它们能够正确、合理的使用公共资源。

    信号量可以分为几类:

    ² 二进制信号量(binary semaphore):只允许信号量取0或1值,其同时只能被一个线程获取。

    ² 整型信号量(integer semaphore):信号量取值是整数,它可以被多个线程同时获得,直到信号量的值变为0。

    ² 记录型信号量(record semaphore):每个信号量s除一个整数值value(计数)外,还有一个等待队列List,其中是阻塞在该信号量的各个线程的标识。当信号量被释放一个,值被加一后,系统自动从等待队列中唤醒一个等待中的线程,让其获得信号量,同时信号量再减一。

    信号量通过一个计数器控制对共享资源的访问,信号量的值是一个非负整数,所有通过它的线程都会将该整数减一。如果计数器大于0,则访问被允许,计数器减1;如果为0,则访问被禁止,所有试图通过它的线程都将处于等待状态。

    计数器计算的结果是允许访问共享资源的通行证。因此,为了访问共享资源,线程必须从信号量得到通行证, 如果该信号量的计数大于0,则此线程获得一个通行证,这将导致信号量的计数递减,否则,此线程将阻塞直到获得一个通行证为止。当此线程不再需要访问共享资源时,它释放该通行证,这导致信号量的计数递增,如果另一个线程等待通行证,则那个线程将在那时获得通行证。

    Semaphore可以被抽象为五个操作:

    - 创建 Create

    - 等待 Wait:

    线程等待信号量,如果值大于0,则获得,值减一;如果只等于0,则一直线程进入睡眠状态,知道信号量值大于0或者超时。

    -释放 Post

    执行释放信号量,则值加一;如果此时有正在等待的线程,则唤醒该线程。

    -试图等待 TryWait

    如果调用TryWait,线程并不真正的去获得信号量,还是检查信号量是否能够被获得,如果信号量值大于0,则TryWait返回成功;否则返回失败。

    -销毁 Destroy

    信号量,是可以用来保护两个或多个关键代码段,这些关键代码段不能并发调用。在进入一个关键代码段之前,线程必须获取一个信号量。如果关键代码段中没有任何线程,那么线程会立即进入该框图中的那个部分。一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。为了完成这个过程,需要创建一个信号量,然后将Acquire Semaphore VI以及Release Semaphore VI分别放置在每个关键代码段的首末端。确认这些信号量VI引用的是初始创建的信号量。

    动作系统

    Win32

    POSIX

    创建

    CreateSemaphore

    sem_init

    等待

    WaitForSingleObject

    sem _wait

    释放

    ReleaseMutex

    sem _post

    试图等待

    WaitForSingleObject

    sem _trywait

    销毁

    CloseHandle

    sem_destroy

    互斥量和信号量的区别

    1. 互斥量用于线程的互斥,信号量用于线程的同步。

    这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。

    互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

    同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源

    2. 互斥量值只能为0/1,信号量值可以为非负整数。

    也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。

    3. 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

    信号量是最早出现的用来解决进程同步与互斥问题的机制(也可实现进程通信),包括一个称为信号量的变量及对它进行的两个原语操作。信号量为一个整数,我们设这个信号量为:sem。很显然,我们规定在sem大于等于零的时候代表可供并发进程使用的资源实体数,sem小于零的时候,表示正在等待使用临界区的进程的个数。根据这个原则,在给信号量附初值的时候,我们显然就要设初值大于零。

    p操作和v操作是不可中断的程序段,称为原语。P,V原语中P是荷兰语的Passeren,相当于英文的pass, V是荷兰语的Verhoog,相当于英文中的incremnet。

    且在P,V愿语执行期间不允许有中断的发生。

    首先应弄清PV操作的含义:PV操作由P操作原语和V操作原语组成(原语是不可中断的过程),对信号量进行操作,具体定义如下:

    P(S):①将信号量S的值减1,即S=S-1;②如果S>=0,则该进程继续执行;否则该进程置为等待状态,排入等待队列。

    V(S):①将信号量S的值加1,即S=S+1;②如果S>0,则该进程继续执行;否则释放队列中第一个等待信号量的进程。

    PV操作的意义:我们用信号量及PV操作来实现进程的同步和互斥。PV操作属于进程的低级通信。

    什么是信号量?信号量(semaphore)的数据结构为一个值和一个指针,指针指向等待该 信号量的下一个进程。信号量的值与相应资源的使用情况有关。当它的值大于0时,表示当前可用资源的数量;当它的值小于0时,其绝对值表示等待使用该资源的进程个数。注意,信号量的值仅能由PV操作来改变。

        一般来说,信号量S>=0时,S表示可用资源的数量。执行一次P操作意味着请求分配一个单位资源,因此S的值减1;

    当S<0时,表示已经没有可用资源,请求者必须等待别的进程释放该类资源,它才能运行下去。而执行一个V操作意味着释放一个单位资源,因此S的值加1;

    若S<=0,表示有某些进程正在等待该资源,因此要唤醒一个等待状态的进程,使之运行下去。

    对信号量有4种操作(include<semaphore>):
    1. 初始化(initialize),int set_init(sem_t *sem, int pshared, unsigned int value);//第二参数为0表示进程间不共享
    2. 等信号(wait),int sem_wait(sem_t *sem);//信号量大于1时,减一并返回;小于1时线程阻塞。
    3. 给信号(signal)int sem_post(sem_t *sem);//信号量加一
    4. 清理(destory) int sem_destory(sem_t *sem);

    使用信号量实现生产者-消费者例子:

    #define BUFFER_SIZE 16 // 缓冲区数量  
      
    struct prodcons  
    {  
        // 缓冲区相关数据结构  
        int buffer[BUFFER_SIZE]; /* 实际数据存放的数组*/  
        int readpos, writepos; /* 读写指针*/  
        sem_t empty; /* 缓冲区非空的条件变量 */  
        sem_t occupied; /* 缓冲区未满的条件变量 */  
        sem_t s_put;/*输入互斥信号量*/  
        sem_t s_take;/*取出互斥信号量*/  
    };  
    /* 初始化缓冲区结构 */  
    void init(struct prodcons *b)  
    {  
        b->readpos = 0;  
        b->writepos = 0;  
        sem_init(&b->empty,0,BUFFER_SIZE);  
        sem_init(&b->occupied,0,0);  
        sem_init(&b->s_put,0,1);//二进制信号量,相当于互斥锁  
        sem_init(&b->s_take,0,1);  
    }  
    /* 将产品放入缓冲区,这里是存入一个整数*/  
    void put(struct prodcons *b, int data)  
    {  
        /*查看空位信号量,是否可以放入产品*/  
        sem_wait(&b->empty);  
        /*查看是否有其他线程正在放入,同一时刻只能一个线程放入*/  
        sem_wait(&b->s_put);  
        /* 写数据,并移动指针 */  
        b->buffer[b->writepos] = data;  
        b->writepos++;  
        if (b->writepos >= BUFFER_SIZE)  
            b->writepos = 0;  
        sem_post(&b->s_put);//解除互斥  
        sem_post(&b->occupied);//放入完成,被占位置信号量加一  
          
    }   
    /* 从缓冲区中取出整数*/  
    int get(struct prodcons *b)  
    {  
        int data;  
        /*查看被占信号量,是否有产品可以拿出*/  
        sem_wait(&b->occupied);  
        /*查看是否有其他线程正在拿出,同一时刻只能一个线程拿出*/  
        sem_wait(&b->s_take);  
        /* 读数据,移动读指针*/  
        data = b->buffer[b->readpos];  
        b->readpos++;  
        if (b->readpos >= BUFFER_SIZE)  
            b->readpos = 0;  
        sem_post(&b->s_take);//解除互斥  
        sem_post(&b->empty);//拿出完成,被空位信号量加一  
        return data;  
    }  
      
    /* 测试:生产者线程将1 到100 的整数送入缓冲区,消费者线 
       程从缓冲区中获取整数,两者都打印信息*/  
    #define OVER ( - 1)  
    struct prodcons buffer;  
    void *producer(void *data)  
    {  
        int n;  
        for (n = 0; n < 100; n++)  
        {  
            printf("%d --->
    ", n);  
            put(&buffer, n);  
        } put(&buffer, OVER);  
        return NULL;  
    }  
      
    void *consumer(void *data)  
    {  
        int d;  
        while (1)  
        {  
            d = get(&buffer);  
            if (d == OVER)  
                break;  
            printf("--->%d 
    ", d);  
        }  
        return NULL;  
    }  
      
    int main(void)  
    {  
        pthread_t th_a, th_b;  
        void *retval;  
        init(&buffer);  
        /* 创建生产者和消费者线程*/  
        pthread_create(&th_a, NULL, producer, 0);  
        pthread_create(&th_b, NULL, consumer, 0);  
        /* 等待两个线程结束*/  
        pthread_join(th_a, &retval);  
        pthread_join(th_b, &retval);  
        return 0;  
    }  
    

      

    互斥锁:

      互斥锁是一种保护机制。上锁后其他线程不能进入保护区域的代码,直到锁被释放。

    信号量:

      信号量是一种同步机制。信号量的值代表可用的资源数目,当值大于0代表有可用资源,则允许继续操作,否则线程阻塞,等待可用资源。

    当可用资源是1时,信号量与互斥锁基本没区别,都起保护作用。当资源数大于1,则当信号量大于0时线程都可进行操作。如果资源大于1时使用互斥锁,则就算资源数大于1时,也只能有一个线程进入操作,其余线程必须阻塞。

    信号量可用于进程通信和线程通信,而互斥锁只能用于线程通信。

        但是还是觉得互斥锁就可以认为是信号量的特例。

        现在有种突然明白什么是利用信号量进行多线程同步的含义了。

  • 相关阅读:
    关于“jdk”版本不支持问题的总结
    Linux系统下jdk卸载安装、配置
    weblogic-jdk 问题
    MCU有哪些复位因素
    MCU固件升级(OTA)的几种Flash划分方式
    003_Linux常用命令之文件操作
    002_Linux常用命令之目录操作
    001_Linux常用命令之ls命令
    dup与dup2函数
    Linux 系统查询机器最近重启时间命令
  • 原文地址:https://www.cnblogs.com/swing07/p/8299173.html
Copyright © 2020-2023  润新知