• futex机制介绍


    https://www.jianshu.com/p/d534f6c1fc5d

    1、概念

    futex: a sort of fast, user-space mutual exclusion primitive.

    Futex是一种用户态和内核态混合的同步机制。首先,同步的进程间通过mmap共享一段内存,futex变量就位于这段共享的内存中且操作是原子的,当进程尝试进入互斥区或者退出互斥区的时候,先去查看共享内存中的futex变量,如果没有竞争发生,则只修改futex,而不用再执行系统调用了。当通过访问futex变量告诉进程有竞争发生,则还是得执行系统调用去完成相应的处理(wait 或者 wake up)。简单的说,futex就是通过在用户态的检查,(motivation)如果了解到没有竞争就不用陷入内核了,大大提高了low-contention时候的效率。

    https://lwn.net/Articles/172149/

    https://lwn.net/Articles/360699/

    2、futex的由来

    为什么要有futex,他解决什么问题?何时加入内核的?我们来看下

    经研究发现,很多同步是无竞争的,即某个进程进入互斥区,到再从某个互斥区出来这段时间,常常是没有进程也要进这个互斥区或者请求同一同步变量的。但是在这种情况下,这个进程也要陷入内核去看看有没有人和它竞争,退出的时侯还要陷入内核去看看有没有进程等待在同一同步变量上。这些不必要的系统调用(或者说内核陷入)造成了大量的性能开销。为了解决这个问题,Futex就应运而生。

    前面的概念已经说了,futex是一种用户态和内核态混合同步机制,为什么会是用户态+内核态,听起来有点复杂,由于我们应用程序很多场景下多线程都是非竞争的,也就是说多任务在同一时刻同时操作临界区的概率是比较小的,大多数情况是没有竞争的,在早期内核同步互斥操作必须要进入内核态,由内核来提供同步机制,这就导致在非竞争的情况下,互斥操作扔要通过系统调用进入内核态。

    我们来看一下程序

    程序1:

    pthread_mutex_t lock;

    int count = 0;

    void thread1()

    {

        while(1)

        {

            pthread_mutex_lock(&lock);

            /* do something */

            count++;

            pthread_mutex_unlock(&lock);

        }

    }

    void thread2()

    {

        while(1)

        {

            sleep(60);

            pthread_mutex_lock(&lock);

            count = 0;

            pthread_mutex_unlock(&lock);

        }

    }

    pthread_create(&tid1, NULL, thread1, NULL);

    pthread_create(&tid2, NULL, thread1, NULL);

    假设系统中有2个程序,一个线程在滴答自增,一个线程周期性清除计数器。显然两个线程同时进入临界区的几率相当小,在未实现futex机制之前,每次调用pthread_mutex_lock和unlock都要通过syscall进入内核,内核检查该锁的拥有者,发现没有人持则返回到用户态,因为大部分时间2个线程并没有争抢互斥锁。显然大部分时间在做无用功,时间浪费在user->kernel和kernel→usr的切换,显然这个锁的性能不太好,因为存在大量user→kernel、kernel→usr的切换,例子可能不太恰当,get到点就好。

    那么如何解决这个问题?

    就像前面说的,采用用户态+内核态混合机制,在用户态使用原子操作,对持有锁的持有情况进行判断,如果锁可以占用,那么更新锁的状态并直接占用,不需要进入内核态,如果锁已经占用,则进入内核态挂起当前任务,事实上这些操作对程序员不可见的,一般都是由C库提实现好了。

    Glibc中常用的线程同步方式举例:

    Semaphore

    变量定义:    sem_t sem;

    初始化:      sem_init(&sem,0,1);

    进入加锁:    sem_wait(&sem);

    退出解锁:    sem_post(&sem);

    Mutex

    变量定义:    pthread_mutex_t mut;

    初始化:      pthread_mutex_init(&mut,NULL);

    进入加锁:    pthread_mutex_lock(&mut);

    退出解锁:    pthread_mutex_unlock(&mut);

    这些用于同步的函数和futex有什么关系?下面让我们来看一看:

    以Semaphores为例,

    进入互斥区的时候,会执行sem_wait(sem_t *sem),sem_wait的实现如下:

    int sem_wait (sem_t *sem)

    {

    int *futex = (int *) sem;

    if (atomic_decrement_if_positive (futex) > 0)

    return 0;

    int  err = lll_futex_wait (futex, 0);

    return -1;

    )

    atomic_decrement_if_positive()的语义就是如果传入参数是正数就将其原子性的减一并立即返回。如果信号量为正,在Semaphores的语义中意味着没有竞争发生,如果没有竞争,就给信号量减一后直接返回了。

    如果传入参数不是正数,即意味着有竞争,调用lll_futex_wait(futex,0),lll_futex_wait是个宏,展开后为:

    #define lll_futex_wait(futex, val) \

    ({                                          \

    __asm __volatile (LLL_EBX_LOAD                          \

    LLL_ENTER_KERNEL                          \

    LLL_EBX_LOAD                          \

    : “=a” (__status)                          \

    : “0″ (SYS_futex), LLL_EBX_REG (futex), “S” (0),          \

    “c” (FUTEX_WAIT), “d” (_val),                  \

    “i” (offsetof (tcbhead_t, sysinfo))              \

    : “memory”);                          \

    …                                      \

    })

    可以看到当发生竞争的时候,sem_wait会调用SYS_futex系统调用,并在val=0的时候执行FUTEX_WAIT,让当前线程休眠。2002年的ols文档,在linux-2.5.7引入了futex。

    https://www.kernel.org/doc/ols/2002/ols2002-pages-479-495.pdf



    作者:superme_
    链接:https://www.jianshu.com/p/d534f6c1fc5d
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    NSURLSession的文件下载
    JSON解析(序列化和反序列化)
    NSURLSession的知识小记
    RunLoop的知识小记
    NSCach 的知识小记
    多图片下载综合案例-磁盘缓存处理
    模仿UIApplication创建单例
    LayoutSubviews的调用
    setValueForKeysWithDictionary的底层实现
    剑指offer 20:顺时针打印矩阵
  • 原文地址:https://www.cnblogs.com/sunupo/p/16477710.html
Copyright © 2020-2023  润新知