精华内容
下载资源
问答
  • Linux 互斥锁、原子操作实现原理

    万次阅读 多人点赞 2016-09-18 14:42:26
    futex(快速用户区互斥的简称)是一个在Linux上实现锁定和构建高级抽象锁如...在Linux下,信号量和线程互斥锁的实现都是通过futex系统调用。 futex(快速用户区互斥的简称)是一个在Linux上实现锁定和构建高级抽象锁

    futex(快速用户区互斥的简称)是一个在Linux上实现锁定和构建高级抽象锁如信号量和POSIX互斥的基本工具。它们第一次出现在内核开发的2 5 7版;其语义在2 5 40固定下来,然后在2 6 x系列稳定版内核中出现。

    在Linux下,信号量和线程互斥锁的实现都是通过futex系统调用。

    futex(快速用户区互斥的简称)是一个在Linux上实现锁定和构建高级抽象锁如信号量和POSIX互斥的基本工具。它们第一次出现在内核开发的2.5.7版;其语义在2.5.40固定下来,然后在2.6.x系列稳定版内核中出现。

    Futex 是fast userspace mutex的缩写,意思是快速用户空间互斥体。Linux内核把它们作为快速的用户空间的锁和信号量的预制构件提供给开发者。Futex非常基础,借助其自身的优异性能,构建更高级别的锁的抽象,如POSIX互斥体。大多数程序员并不需要直接使用Futex,它一般用来实现像NPTL这样的系统库。

    Futex 由一块能够被多个进程共享的内存空间(一个对齐后的整型变量)组成;这个整型变量的值能够通过汇编语言调用CPU提供的原子操作指令来增加或减少,并且一个进程可以等待直到那个值变成正数。Futex 的操作几乎全部在应用程序空间完成;只有当操作结果不一致从而需要仲裁时,才需要进入操作系统内核空间执行。这种机制允许使用 futex 的锁定原语有非常高的执行效率:由于绝大多数的操作并不需要在多个进程之间进行仲裁,所以绝大多数操作都可以在应用程序空间执行,而不需要使用(相对高代价的)内核系统调用。

    futex保存在用户空间的共享内存中,并且通过原子操作进行操作。在大部分情况下,资源不存在争用的情况下,进程或者线程可以立刻获得资源成功,实际上就没有必要调用系统调用,陷入内核了。实际上,futex的作用就在于减少系统调用的次数,来提高系统的性能。

    线程互斥锁pthread_mutex_t的实现原理:

    pthread_mutex_lock:
    atomic_dec(pthread_mutex_t.value);
    if(pthread_mutex_t.value!=0)
    futex(WAIT)
    else
    success

    pthread_mutex_unlock:
    atomic_inc(pthread_mutex_t.value);
    if(pthread_mutex_t.value!=1)
    futex(WAKEUP)
    else
    success

    信号量sem_t的实现原理:

    sem_wait(sem_t *sem)
    {
    for (;;) {

    if (atomic_decrement_if_positive(sem->count))
    break;

    futex_wait(&sem->count, 0)
    }
    }

    sem_post(sem_t *sem)
    {
    n = atomic_increment(sem->count);
    // Pass the new value of sem->count
    futex_wake(&sem->count, n + 1);
    }

    对比,pthread_mutex_unlock()和sem_post()的实现,我们发现一个不同点,sem_post()无论如何都会调用 futex_wake(),进行系统调用。但是pthread_mutex_unlock()却符合futex的初衷,只有在需要仲裁的时候才调用 futex_wake()。那么什么是仲裁条件呢?

    前面说过信号量和线程互斥锁语义上的区别在于信号量的value>=0,而线程互斥锁的value可以为负数。
    对于lock操作,这两个倒是没有多少差别。信号量只要value>0就可以获得资源,线程互斥锁需要value=1。
    但是对于unlock操作,这两个就有一些差别了。信号量和线程互斥锁,都会增加对应的value。如果加1后,value为1,对于线程互斥锁来讲,实际上表明资源可用,并且之前没有其他的线程在等待这个资源;否则说明还有其他线程在等待这个资源,需要调用futex系统调用唤醒它们。但是对于信号量,由于value必须>=0。那么加1后,即使value为1,也无法判定现在没有其他的进程或线程正在等待资源,所以必须调用futex系统调用。例如:

    #include 
    #include 
    #include 

    sem_t sem_a;
    void *task1();

    int main(void)
    {
    int ret=0;
    pthread_t thrd1;
    pthread_t thrd2;
    sem_init(&sem_a,0,1);
    ret=pthread_create(&thrd1,NULL,task1,NULL); //创建子线程
    ret=pthread_create(&thrd2,NULL,task1,NULL); //创建子线程
    pthread_join(thrd1,NULL); //等待子线程结束
    pthread_join(thrd2,NULL); //等待子线程结束
    }

    void *task1()
    {
    int sval = 0;
    sem_wait(&sem_a); //持有信号量
    sleep(5); //do_nothing
    sem_getvalue(&sem_a,&sval);
    printf("sem value = %d/n",sval);
    sem_post(&sem_a); //释放信号量
    }

    上面sem的value初始化为1,但是有两个线程争用资源。那么第一个线程获得资源成功,当它unlock的时候,sem的value变为1。但是,这个时候,实际上还有一个线程在等待资源。因此,必须要进行futex_wake()系统调用,唤醒等待资源的线程。

    原子操作实现原理:


    关于x86原子操作指令的说明:
    cmpxchg 比较交换指令,其语义为:

    int CompareAndExchange(int *ptr, int old, int new)
    {
    int actual = *ptr;
    if (actual == old)
    *ptr = new;
    return actual;
    }

    使用此原子操作可以实现自旋锁,之前有一篇文章中描述了实现:

    void lock(lock_t *lock) {
    while (CompareAndExchange(&lock->flag, 0, 1) == 1)
    ; // spin
    }
    void unlock(lock_t *lock) {
    lock->flag = 0;
    }

    关于smp下的原子操作的一些说明:
    原子操作是不可分割的,在执行完毕不会被任何其它任务或事件中断。在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作都可以认为是" 原子操作",因为中断只能发生于指令之间。这也是某些CPU指令系统中引入了test_and_set、test_and_clear等指令用于临界资源互斥的原因。在对称多处理器(Symmetric Multi-Processor)结构中就不同了,由于系统中有多个处理器在独立地运行,即使能在单条指令中完成的操作也有可能受到干扰。
    在x86 平台上,CPU提供了在指令执行期间对总线加锁的手段。CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。
    当然,并不是所有的指令前面都可以加lock前缀的,只有ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG,DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, 和 XCHG指令前面可以加lock指令,实现原子操作。

    处理器级的原子操作实现

    术语定义

    术语

    英文

    解释

    缓存行

    Cac he line

    缓存的最小操作单位

    比较并交换

    Compare and Swap

    CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换。

    CPU流水线

    CPU pipeline

    CPU流水线的工作方式就象工业生产上的装配流水线,在CPU中由5~6个不同功能的电路单元组成一条指令处理流水线,然后将一条X86指令分成5~6步后再由这些电路单元分别执行,这样就能实现在一个CPU时钟周期完成一条指令,因此提高CPU的运算速度。

    内存顺序冲突

    Memory order violation

    内存顺序冲突一般是由假共享引起,假共享是指多个CPU同时修改同一个缓存行的不同部分而引起其中一个CPU的操作无效,当出现这个内存顺序冲突时,CPU必须清空流水线。


    32位IA-32处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。处理器如何实现原子操作

    处理器自动保证基本内存操作的原子性

    首先处理器会自动保证基本的内存操作的原子性。处理器保证从系统内存当中读取或者写入一个字节是原子的,意思是当一个处理器读取一个字节时,其他处理器不能访问这个字节的内存地址。奔腾6和最新的处理器能自动保证单处理器对同一个缓存行里进行16/32/64位的操作是原子的,但是复杂的内存操作处理器不能自动保证其原子性,比如跨总线宽度,跨多个缓存行,跨页表的访问。但是处理器提供总线锁定和缓存锁定两个机制来保证复杂内存操作的原子性。

    使用总线锁保证原子性

    第一个机制是通过总线锁保证原子性。如果多个处理器同时对共享变量进行读改写(i++就是经典的读改写操作)操作,那么共享变量就会被多个处理器同时进行操作,这样读改写操作就不是原子的,操作完之后共享变量的值会和期望的不一致,举个例子:如果i=1,我们进行两次i++操作,我们期望的结果是3,但是有可能结果是2。如下图

    (例1)

    原因是有可能多个处理器同时从各自的缓存中读取变量i,分别进行加一操作,然后分别写入系统内存当中。那么想要保证读改写共享变量的操作是原子的,就必须保证CPU1读改写共享变量的时候,CPU2不能操作缓存了该共享变量内存地址的缓存。

    处理器使用总线锁就是来解决这个问题的。所谓总线锁就是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以独占使用共享内存。

    使用缓存锁保证原子性

    第二个机制是通过缓存锁定保证原子性。在同一时刻我们只需保证对某个内存地址的操作是原子性即可,但总线锁定把CPU和内存之间通信锁住了,这使得锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁定的开销比较大,最近的处理器在某些场合下使用缓存锁定代替总线锁定来进行优化。

    频繁使用的内存会缓存在处理器的L1,L2和L3高速缓存里,那么原子操作就可以直接在处理器内部缓存中进行,并不需要声明总线锁,在奔腾6和最近的处理器中可以使用“缓存锁定”的方式来实现复杂的原子性。所谓“缓存锁定”就是如果缓存在处理器缓存行中内存区域在LOCK操作期间被锁定,当它执行锁操作回写内存时,处理器不在总线上声言LOCK#信号,而是修改内部的内存地址,并允许它的缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改被两个以上处理器缓存的内存区域数据,当其他处理器回写已被锁定的缓存行的数据时会起缓存行无效,在例1中,当CPU1修改缓存行中的i时使用缓存锁定,那么CPU2就不能同时缓存了i的缓存行。

    但是有两种情况下处理器不会使用缓存锁定。第一种情况是:当操作的数据不能被缓存在处理器内部,或操作的数据跨多个缓存行(cache line),则处理器会调用总线锁定。第二种情况是:有些处理器不支持缓存锁定。对于Inter486和奔腾处理器,就算锁定的内存区域在处理器的缓存行中也会调用总线锁定。

    以上两个机制我们可以通过Inter处理器提供了很多LOCK前缀的指令来实现。比如位测试和修改指令BTS,BTR,BTC,交换指令XADD,CMPXCHG和其他一些操作数和逻辑指令,比如ADD(加),OR(或)等,被这些指令操作的内存区域就会加锁,导致其他处理器不能同时访问它。

    最新文章
    <iframe id="iframeu2616323_0" src="http://pos.baidu.com/qcwm?sz=979x294&rdid=2616323&dc=2&di=u2616323&dri=0&dis=0&dai=4&ps=5953x0&coa=at%3D3%26hn%3D0%26wn%3D0%26imgRatio%3D1.7%26scale%3D20.6%26pat%3D6%26tn%3Dtemplate_inlay_all_mobile_lu_native%26rss1%3D%2523FFFFFF%26adp%3D1%26ptt%3D0%26titFF%3D%2525E5%2525BE%2525AE%2525E8%2525BD%2525AF%2525E9%25259B%252585%2525E9%2525BB%252591%26titFS%3D14%26rss2%3D%2523000000%26titSU%3D0%26ptbg%3D70%26ptp%3D0&dcb=BAIDU_SSP_define&dtm=HTML_POST&dvi=0.0&dci=-1&dpt=none&tsr=0&tpr=1474180466404&ti=Linux%20%E4%BA%92%E6%96%A5%E9%94%81%E3%80%81%E5%8E%9F%E5%AD%90%E6%93%8D%E4%BD%9C%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86&ari=2&dbv=2&drs=1&pcs=1903x979&pss=1903x5954&cfv=0&cpl=5&chi=1&cce=true&cec=GBK&tlm=1474180502&rw=979&ltu=http%3A%2F%2Fm.2cto.com%2Fos%2F201608%2F541708.html&ltr=https%3A%2F%2Fwww.google.com.hk%2F&ecd=1&psr=1920x1080&par=1920x1040&pis=-1x-1&ccd=24&cja=false&cmi=7&col=zh-CN&cdo=-1&tcn=1474180502&qn=2b0df832cfcf144e&tt=1474180466347.35929.42489.42492" width="979" height="294" align="center,center" vspace="0" hspace="0" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" allowtransparency="true" style="box-sizing: border-box; border-width: 0px; border-style: initial; vertical-align: bottom; margin: 0px;"></iframe>
    展开全文
  • Linux互斥锁的使用

    2020-08-28 23:12:09
    互斥锁原理 1. 互斥锁原理 如上图所示:如果多个线程要访问一段共享资源的内存区域时,其中一个线程(如图中线程1)首先读取共享区域时,会在共享区域外设置一把互斥锁,其它线程阻塞在互斥锁处,线程1结束共享...

    目录

    1. 互斥锁原理

    2. 互斥锁相关函数

    3. 互斥锁使用过程

    4. 互斥锁使用示例

    5. 死锁


    1. 互斥锁原理

    如上图所示:如果多个线程要访问一段共享资源的内存区域时,其中一个线程(如图中线程1)首先读取共享区域时,会在共享区域外设置一把互斥锁,其它线程阻塞在互斥锁处,线程1结束共享资源的访问后,会解锁该内存区域,此时其它的线程才可以继续访问共享资源的内存区域。本来多线程访问数据时是并行访问内存区域的,加上互斥锁后变为串行处理。

    多线程编程是建议使用互斥锁,这样可以对公共区域的数据进行保护。互斥锁的缺点就是串行,数据访问的效率会有一定的降低。

    2. 互斥锁相关函数

    1.创建互斥锁函数
    pthread_mutex_t mutex;
    
    2.初始化互斥锁函数:
    int pthread_mutex_init(pthread_mutex_t *restrict mutex, 
                           const pthread_mutexattr_t *restrict attr
                            ); 
        参数1:传出参数,调用是创建的互斥锁变量。
        参数2:传入参数,互斥锁属性,一般为NULL表示为默认属性
        这里的restrict关键字,表示指针指向的内容只能通过这个指针进行修改
        restrict关键字作用可以理解为:
    	    用来限定指针变量。被该关键字限定的指针变量所指向的内存操作,必须由本指针完成。
        
        初始化可以使用静态初始化和动态初始化,示例:
        pthread_mutex_t mutex;
    		1. pthread_mutex_init(&mutex, NULL);   			动态初始化。
    		2. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;	静态初始化。
    
    3.加锁
    int pthread_mutex_lock(pthread_mutex_t *mutex);
         参数:mutex
         没有上锁,当前线程会将这把锁上锁
         已经上锁,当前线程会阻塞在此
                  锁被打开之后,线程会解除阻塞
    
    4.尝试加锁
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
          参数:mutex
          没有上锁:同上
          已经上锁,不阻塞返回
    
    5.解锁
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    
    6.销毁一个互斥锁
    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    

    3. 互斥锁使用过程

    (1)创建互斥锁:pthread_mutex_t mutex;

    (2)初始化互斥锁: pthread_mutex_init(&mutex, NULL);

    (3)找到线程共同操作的共享资源

               加锁:prhread_mutex_lock(&mutex);     //如果共享区域已经上锁,会阻塞线程

                          pthread_mutex_trylock(&mutex);   //如果共享区域已经上锁,直接返回,不会阻塞。

    (4)访问内存区域

    (5)解锁:pthread_mutex_unlock(&mutex);

    (6)销毁:pthread_mutex_destory(&mutex);

     使用注意:

    尽量保证锁的粒度,越小越好,一般访问共享数据前加锁,访问结束立即解锁

            互斥锁,本质是结构体。 我们可以看成整数。 初值为 1。(pthread_mutex_init() 函数调用成功。)

            加锁: --操作, 阻塞线程。

            解锁: ++操作, 唤醒阻塞在锁上的线程。

            try锁:尝试加锁,成功--。失败,返回。同时设置错误号 EBUSY

    4. 互斥锁使用示例

    pthread_mutex.c:主线程建立两个子线程对全局变量进行++操作。

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <string.h>
    #include <pthread.h>
    
    
    #define MAX 10000
    // 全局变量
    int number;
    //创建一把互斥锁
    pthread_mutex_t mutex;
    
    
    // 线程A处理函数
    void* funcA_num(void* arg)
    {
        for(int i=0; i<MAX; ++i)
        {
            //访问全局变量之前加锁
            //如果mutex被锁上了,代码阻塞在当前位置
            pthread_mutex_lock(&mutex);
            int cur = number;
            cur++;
            number = cur;
            printf("Thread A, id = %lu, number = %d\n", pthread_self(), number);
            //解锁
            pthread_mutex_unlock(&mutex);
            usleep(10);
        }
     
        return NULL;
    }
    // 线程B处理函数 
    void* funcB_num(void* arg)
    {
        for(int i=0; i<MAX; ++i)
        {
            pthread_mutex_lock(&mutex);
            int cur = number;
            cur++;
            number = cur;
            printf("Thread B, id = %lu, number = %d\n", pthread_self(), number);
                    pthread_mutex_unlock(&mutex);
            usleep(10);
        }
     
        return NULL;
    }
    
    /*主函数创建两个线程,分别操作共享区域内存:全局变量number*/
    int main(int argc, const char* argv[])
    {
        pthread_t p1, p2;
            //初始化互斥锁
        pthread_mutex_init(&mutex,NULL);
     
        // 创建两个子线程
        pthread_create(&p1, NULL, funcA_num, NULL);
        pthread_create(&p2, NULL, funcB_num, NULL);
     
        // 阻塞,资源回收
        pthread_join(p1, NULL);
        pthread_join(p2, NULL);
     
            //释放互斥锁资源
        pthread_mutex_destroy(&mutex);
     
        return 0;
    }

    运行结果:AB两个线程逐个访问共享内存区域。

    5. 死锁

    程序运行是要避免死锁的情况。通常出现死锁的情况有两种:
           1.  对一个锁反复lock:对一个地方使用了两次锁,没有及时解锁

    2. 两个线程,各自持有一把锁,请求另一把锁,如下图所示:A,B两个线程访问AB两个共享数据区域时。

    解决方法:

            1. 让线程按照一定的顺序去访问共享资源

            2. 在访问的其他锁的时候,需要先将字节的锁解开

            3.trylock方式定义锁

    展开全文
  • 本文参考——...互斥锁大都会使用,但是要了解其原理就要花费一番功夫了。尽管我们说互斥锁是用来保护一个临界区,实际上保护的是临界区中被操纵的数据。 互斥锁还是分为三类:快速互斥锁...

    本文参考——http://www.bitscn.com/os/linux/201608/725217.html

    http://blog.csdn.net/jianchaolv/article/details/7544316

    引言

    互斥锁大都会使用,但是要了解其原理就要花费一番功夫了。尽管我们说互斥锁是用来保护一个临界区,实际上保护的是临界区中被操纵的数据。

    互斥锁还是分为三类:快速互斥锁/递归互斥锁/检测互斥锁

    futex

    要想了解互斥锁的内部实现,先来了解一下futex(fast Userspace mutexes)的作用。

    内核态和用户态的混合机制。

    还没有futex的时候,内核是如何维护同步与互斥的呢?系统内核维护一个对象,这个对象对所有进程可见,这个对象是用来管理互斥锁并且通知阻塞的进程。如果进程A要进入临界区,先去内核查看这个对象,有没有别的进程在占用这个临界区,出临界区的时候,也去内核查看这个对象,有没有别的进程在等待进入临界区。

    互斥锁

    1、互斥锁的结构?

    在futex的基础上用的内存共享变量来实现的。

    2、不能锁住的时候,是如何进入休眠,又如何等待被唤醒的呢?

    进入锁的时候就会区检查那个共享变量,如果不能获取锁,就会通过futex系统调用进入休眠。如果有人释放锁,就会通过futex来唤醒。

    3、互斥锁的属性?

    指定锁的适用范围。

    4、经典案例——生产者消费者

    转载于:https://www.cnblogs.com/sylz/p/6030201.html

    展开全文
  • 文章目录互斥互斥锁原理 互斥 互斥锁原理 1.互斥锁的底层是一个互斥量,而互斥量的本质是一个计数器,计数器的取值只有两种,一种是1,一种是0。1表示当前临界资源可以被访问,0表示当前临界资源不可以被访问。 ...

    1. 互斥

    1.1 互斥锁的原理

      互斥锁的底层是一个互斥量,而互斥量的本质是一个计数器,计数器的取值只有两种,一种是1,一种是0。1表示当前临界资源可以被访问,0表示当前临界资源不可以被访问。

    获取/释放互斥锁的逻辑:
      1.调用加锁接口,加锁接口内部判断计数器的值是否为1,如果为1,则能访问,并且如果加锁成功之后,就会将计数器的值从1变为0,如果为0,则不能访问。
      2.调用解锁逻辑,计数器的值从0变成1,表示资源可用。

    加锁:
      寄存器和内存当中的计数器的值进行交换,判断寄存器的当中的值是否为1,如果为1,则能够加锁,反之不能。

    1.2 互斥锁的接口

    初始化互斥锁变量
      互斥锁的类型:pthread_mutex_t

    1.2.1 静态初始化

    pthread_mutex_t mutex = PTHREAD_MUTRX_INITIALIZER
      这是一个宏定义,本质上包含多个值
    在这里插入图片描述
      root用户下执行下面语句可以打开查看

    vim /user/include/pthread.h
    vim /usr/include/bits/pthreadtypes.h
    

    1.2.2 动态初始化

    pthread_mutex_init函数
    在这里插入图片描述
      int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t * attr);
      mutex:该参数为出参,由调用者传递一个互斥锁变量的地址,由pthread_mutex_init这个函数进行初始化
      attr:互斥锁的属性信息,一般设置为NULL,采用默认属性
    注意: 动态初始化互斥锁变量的情况需要动态销毁互斥锁(pthread_mutex_destroy),否则就会造成内存泄露。

    1.2.3 加锁

      1.阻塞加锁接口:pthread_mutex_lock函数
    在这里插入图片描述
      int pthread_mutex_lock(pthread_mutex_t *mutex);
    注意:
      如果互斥锁变量当中的计数器的值为1,调用该接口,则加锁成功,该接口调用完毕,函数返回。如果互斥锁变量的那个汇总的计数器的值为0,调用该接口,则调用该接口的执行流阻塞。

      2.非阻塞加锁接口:pthread_mutex_trylock函数
    在这里插入图片描述
      int pthread_mutex_trylock(pthread_mutex_t *mutex);
    注意:
      不管有没有加锁成功,都会返回,所以,需要对加锁结果进行判断,判断是否加锁,如果加锁成功,则操作临界资源,反之,则需要循环获取互斥锁,直到拿到互斥锁。

      3.带有超时时间的加锁接口:pthread_mutex_timedlock
    在这里插入图片描述
       int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);

      超时时间内,如果没有获得互斥锁,则返回,超时时间内,如果获取了互斥锁也直接返回。
    注意:
      带有超时时间的加锁接口还需要引入头文件time.h
    在这里插入图片描述

    1.2.4 解锁接口

      pthread_mutex_unlock函数
    在这里插入图片描述
      int pthread_mutex_unlock(pthread_t* mutex)
    注意:
      不管是阻塞加锁/非阻塞加锁/timedlock加锁成功都可以使用该接口进行解锁。

    1.2.5 销毁接口

      pthread_mutex_destroy
    在这里插入图片描述
      int pthread_mutex_destroy(pthread_t* mutex)
    功能: 释放动态开辟的互斥锁的资源

    1.3 互斥锁的使用

    1.3.1 什么时候要初始化互斥锁?

      在创建工作线程之前,进行初始化互斥锁。

    1.3.2 什么时候进行加锁?

      在执行流访问临界资源之前进行加锁操作。
    注意:
      一个执行流加锁成功之后,再去获取互斥锁,该执行流也会阻塞。加锁之后一定要记得解锁,否则就会导致死锁。

    1.3.3 什么时候解锁?

      在执行流所有有可能退出的地方进行解锁。

    1.3.4 什么时候释放互斥锁资源?

      在所有使用该互斥锁的线程全部退出之后,就可以释放该互斥锁了。

    1.4 代码验证

    #include <stdio.h>
    #include <unistd.h>
    #include <pthread.h>
    
    #define THREAD_NUM 2
    
    int g_tickets = 100000;
    pthread_mutex_t my_lock;
    
    void* MyThreadStart(void* arg)
    {
        while(1)
        {
            pthread_mutex_lock(&my_lock);
            if(g_tickets > 0)
            {
                printf("i have %d, i am %p\n", g_tickets, pthread_self());
                g_tickets--;
            }
            else
            {
                pthread_mutex_unlock(&my_lock);
                pthread_exit(NULL);
            }
            pthread_mutex_unlock(&my_lock);
        }
        return NULL;
    }
    
    int main()
    {
        pthread_mutex_init(&my_lock, NULL);
    
        pthread_t tid[THREAD_NUM];
        for(int i = 0; i < THREAD_NUM; i++)
        {
            int ret = pthread_create(&tid[i], NULL, MyThreadStart, NULL);
            if(ret < 0)
            {
                perror("pthread_create");
                return 0;
            }
        }
    
        for(int i = 0; i < THREAD_NUM; i++)
        {
            pthread_join(tid[i], NULL);
        }
    
        pthread_mutex_destroy(&my_lock);
        printf("phread_join end...\n");
    
        return 0;
    }
    

    在这里插入图片描述
    在这里插入图片描述
      因为我这使用的这个机器只有一个CPU所以,线程只能是并发的执行。

    2. 死锁

    2.1 死锁的定义

      简单的定义:当一个执行流获取到互斥锁之后,并没有进行解锁,就会导致其他执行流由于获取不到锁资源而进行阻塞,我们将这个现象称之为死锁。
      复杂的定义:当线程A获取到互斥锁1,线程B获取到互斥锁2的时候,线程A和线程B同时还想获取对方手中的锁(线程A还想获取互斥锁2,线程B还想获取互斥锁1),此时就会导致死锁。

    2.2 死锁的现象

    在这里插入图片描述

    2.3 死锁的代码

    #include <stdio.h>
    #include <unistd.h>
    #include <pthread.h>
    
    pthread_mutex_t lock_1;
    pthread_mutex_t lock_2;
    
    void* MyThread_A(void* arg)
    {
        pthread_mutex_lock(&lock_1);
        sleep(2);
        pthread_mutex_lock(&lock_2);
    
        return NULL;
    }
    
    void* MyThread_B(void* arg)
    {
        pthread_mutex_lock(&lock_2);
        sleep(2);
        pthread_mutex_lock(&lock_1);
    
        return NULL;
    }
    
    int main()
    {
        pthread_mutex_init(&lock_1, NULL);
        pthread_mutex_init(&lock_2, NULL);
    
        pthread_t tid_A, tid_B;
        pthread_create(&tid_A, NULL, MyThread_A, NULL);
        pthread_create(&tid_B, NULL, MyThread_B, NULL);
    
        pthread_join(tid_A, NULL);
        pthread_join(tid_A, NULL);
    
        pthread_mutex_destroy(&lock_1);
        pthread_mutex_destroy(&lock_2);
    
        return 0;
    }
    

    1.执行死锁的代码
    在这里插入图片描述
    2.查看进程号
    在这里插入图片描述
    3.调试正在运行的进程
    在这里插入图片描述
    4.查看当前线程(主线程)的调用堆栈
    在这里插入图片描述
    5.将查看调用堆栈这个功能应用于所有线程
    在这里插入图片描述
    6.跳转到3号线程
    在这里插入图片描述
    7.查看锁的情况
    在这里插入图片描述

    2.4 死锁的必要条件

      1.不可剥夺:执行流获取了互斥锁之后,除了自己主动释放锁,其他执行流不能解释该互斥锁
      2.循环等待
      3.互斥条件:一个互斥锁只能被一个执行流在同一时刻拥有
      4.请求与保持:“吃着碗里瞧着锅里”

    2.5 预防死锁

      1.破坏必要条件:循环等待或者请求与保持
      2.加锁顺序一致
      3.避免锁没有被释放
      4.资源一次性分配

    2.6 避免死锁的算法

      1.死锁检测算法
      2.银行家算法

    展开全文
  • 所谓的,在计算机里本质上就是一块内存空间。当这个空间被赋值为 1 的时候表示加锁了,被赋值为 0 的时候表示解锁了,仅此而已。 多个线程抢一个,就是抢着要把这块内存赋值为 1 。在一个多核环境里,内存空间...
  • 看了看linux 2.6 kernel的源码,下面结合代码来分析一下在X86体系结构下,互斥锁的实现原理。 代码分析 1. 首先介绍一下互斥锁所使用的数据结构:struct mutex {引用计数器1: 所可以利用。 小于等于0:该锁已被...
  • 其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。在任何时刻最多只能有一个执行单元获得锁,即任何时刻只有一个线程访问对象。如果自旋锁已经被别的执行单元保持(资源被占用)调用者就一直...
  • 一段代码引发的问题 首先,我们来编写一段代码...我们知道,只要是共享的资源,那么它就可以看作临界资源,而临界资源的访问需要同步与互斥机制才能维持正常访问。否则可能会出现数据不一致的问题。 下面,我们来讨.
  • 02 进程间的互斥锁和线程间互斥锁的区别 函数pthread_mutex_init(互斥锁地址, 属性对象地址)在定义一把线程锁的时候第二个参数通常传为NULL,这样该锁默认只能被统一进程下的线程持有。 如果要将其定义为进程之间...
  • 1.互斥锁基本原理        互斥锁提供了对临界资源以互斥方式进行访问的同步机制。简单来说,互斥锁类似于一个布尔变量,它只有“锁定”和“打开”两种状态,在使用临界资源时...
  • 看了看linux 2.6 kernel的源码,下面结合代码来分析一下在X86体系结构下,互斥锁的实现原理。 代码分析 1. 首先介绍一下互斥锁所使用的数据结构:struct mutex {&nbsp;引用计数器&nbsp;1: 所可以利用。 &...
  • 线程安全 概念:多个执行流对同一临界资源进行争抢访问,但是不会造成数据二义 ...互斥锁原理: 本质:互斥锁本质就是一个计数器,只有两种状态标记的计数器0/1 原理:访问临界资源之前,先访问...
  • 互斥锁基本原理 互斥以排他方式防止共享数据被并发修改。互斥锁是一个二元变量,其状态为开锁(允许0)和上锁(禁止1),将某个共享资源与某个特定互斥锁绑定后,对该共享资源的访问如下操作: (1)在访问该资源前...
  • 信号量在创建时需要设置一个初始值,表示同时可以有几个任务可以访问该信号量保护的共享资源,初始值为 1 就变成互斥锁(Mutex),即同时只能有一个任务可以访问信号量保护的共享资源。 一个任务要想...
  • 如何描述线程不安全的现象2.1 正常变量操作的原理2.2 线程不安全现象的描述 1.线程安全 线程安全:多个执行流,访问临界资源,不会导致程序产生二义性 执行流:理解为线程 访问:指的是对临界资源进行操作 临界...
  • 1)Linux下线程,条件变量的使用 ...2)讲了原理,通俗易懂 https://blog.csdn.net/summy_J/article/details/72756926 3)这一篇讲述了在linux怎么分析死锁问题。 https://www.cnblogs.com/y...
  • 线程安全概念:线程间的互斥互斥的实现:互斥锁互斥锁的操作流程、接口介绍:简单案例互斥量(mutex)实现原理探究线程间的同步操作接口介绍简单案例 概念: 在多个执行流中对一个临界资源进行操作访问,而不会造成...
  • 互斥锁互斥锁函数初始化互斥锁的函数函数原型功能:返回值参数mutex:互斥锁,需要我们自己定义。attr:互斥锁的属性加锁解锁函数函数原型功能返回值参数销毁互斥锁函数原型功能返回值代码演示再说说互斥锁初始化...
  • 信号量、互斥锁、自旋锁前言一、信号量1、信号量初始化api2、获取信号量3、释放信号量4、使用案例二、互斥锁互斥锁的API三、自旋锁1、初始化2、获得自旋锁3、释放自旋锁4、判断自旋锁5、自旋锁使用注意事项 ...
  • Linux c++ 多线程编程基础——互斥锁

    千次阅读 2018-04-12 18:27:40
    涉及的范围实在有点广,所以之后分开来慢慢讲解,先说这个互斥锁。 首先是互斥,这是什么呢?说起来就又是一张的内容,详细自己去了解一下,参考书籍《操作系统——精髓与设计原理(第七版)》第五章,我这里将要说...
  • 在操作系统原理的术语中,线程是进程的一条执行路径。线程在Unix系统下,通常被称为轻量级的进程,线程虽然不是进 程,但却可以看作是Unix进程的表亲,所有的线程都是在同一进程空间运行,这也意味着多条线程将共享...
  • 文章目录(1)临界区,临界资源和原子性问题(2)互斥量(锁)A:互斥锁B:锁的作用C:互斥锁实现的原理(3)可重入函数和线程安全A:可重入函数和线程安全B:常见的线程安全和不安全情况C:常见可重入和不可重入的...
  • 线程不安全的原理 举个栗子:我们规定一个场景 假设现在在同一个程序当中有两个线程,线程A和线程B,并且有一个int类型全局变量,值为10;线程A和线程B在各自的入口函数当中对这样一个变量进行++操作。
  • 1.互斥锁原理 2.自旋锁的使用场景 3.三种操作的使用场景与区别 【技术分享篇】epoll的具体实现与epoll线程安全,互斥锁,自旋锁,CAS,原子操作 更多Linux服务器开发高阶完整视频分享,点击链接即可观看:...

空空如也

空空如也

1 2 3 4 5 ... 10
收藏数 181
精华内容 72
关键字:

linux互斥锁原理

linux 订阅