IPC-信号量 以及pthread-mutex

https://ty-chen.github.io/linux-kernel-shm-semaphore/

Linux提供两种信号量:

  • 内核信号量,由内核控制路径使用

  • 用户态进程使用的信号量,这种信号量又分为POSIX信号量和SYSTEM V信号量。

    1. 对POSIX来说,信号量是个非负整数。
    2. 而SYSTEM V信号量则是一个或多个信号量的集合,它对应的是一个信号量结构体,这个结构体是为SYSTEM V IPC服务的,信号量只不过是它的一部分。常用于进程间同步。

    3. POSIX信号量的引用头文件是<semaphore.h>,而SYSTEM V信号量的引用头文件是<sys/sem.h>

  Linux内核的信号量在概念和原理上与用户态的System V的IPC机制信号量是一样的,但是它绝不可能在内核之外的中断使用,它是一种睡眠锁。

如果有一个任务想要获得已经被占用的信号量时,信号量会将其放入一个等待队列(它不是站在外面痴痴地等待而是将自己的名字写在任务队列中)然后让其睡眠。

当持有信号量的进程将信号释放后,处于等待队列中的一个任务将被唤醒(因为队列中可能不止一个任务),并让其获得信号量

ps:在有的系统中Binary semaphore与Mutex是没有差异的。在有的系统上,主要的差异是mutex一定要由获得锁的进程来释放。而semaphore可以由其它进程释放(这时的semaphore实际就是个原子的变量,大家可以加或减),因此semaphore可以用于进程间同步。Semaphore的同步功能是所有 系统都支持的,而Mutex能否由其他进程释放则未定,因此建议mutex只用于保护criticalsection。而semaphore则用于保护某变量,或者同步。

平时用的不多!! 有时间继续看

信号量的核心思想是:内核空间分配一个data管理一个“文件id”, 进程A 和进程B 通过“文件id” 访问数据,同时加上原子操作等实现通信!!

PS:在信号量这种常用的同步互斥手段方面,POSIX在无竞争条件下是不会陷入内核的,而SYSTEM V则是无论何时都要陷入内核,因此性能稍差

  • 1.System V信号量每次执行PV操作时,都需要进行用户态和内核态的切换。
  • 2. POSIX pthread库实现的信号量执行PV操作时,仅当需要时才进行用户态和内核态的切换。具体表述如下:

        2.1 P操作:a) 在用户态“信号量值减一,且值大于等于0”,则无需陷入内核;

                                b) 在用户态“信号量值减一,且值小于0”,则需要陷入内核,并将调用进程插入到该信号量的等待队列,睡眠;

        2.2 V操作:a) 在用户态“信号量值加一,且值大于0”,则无需陷入内核;

                                b) 在用户态“信号量值加一,且值小于等于0”,则需要陷入内核,并唤醒该信号量等待队列上的一个进程;

 来看看POSIX 的pthread_mutex 是怎么实现的!!毕竟用的多 性能要好!

这个用的比较多!!

pthread_mutex_t {
    int __lock; // 锁变量, 传给系统调用futex,用作用户空间的锁变量 mutex状态,0表示未占用,1表示占用
    usigned int __count;  // 可重入的计数  记录owner线程持有锁的次数
    int __owner;   // 被哪个线程占有了  owner线程ID
    int __kind;  // 记录mutex的类型,有以下几个取值  PTHREAD_MUTEX_TIMED_NP 等----
------------------------
}

pthread_mutex_lock

  pthread_mutex_lock调用LLL_UNLOCK(基于Linux的futex), 去拿到锁或阻塞自己. 另外,对可重入的锁进行计数(也叫做递归锁,是指在一个线程中可以多次获取同一把锁).

代码见:https://code.woboq.org/userspace/glibc/nptl/pthread_mutex_lock.c.html

int
__pthread_mutex_lock (pthread_mutex_t *mutex)
{
  /* See concurrency notes regarding mutex type which is loaded from __kind
     in struct __pthread_mutex_s in sysdeps/nptl/bits/thread-shared-types.h.  */
  unsigned int type = PTHREAD_MUTEX_TYPE_ELISION (mutex);
  LIBC_PROBE (mutex_entry, 1, mutex);
  if (__builtin_expect (type & ~(PTHREAD_MUTEX_KIND_MASK_NP
                                 | PTHREAD_MUTEX_ELISION_FLAGS_NP), 0))
    return __pthread_mutex_lock_full (mutex);
  if (__glibc_likely (type == PTHREAD_MUTEX_TIMED_NP))//一般默认缺省值,即普通锁
    {
      FORCE_ELISION (mutex, goto elision);
    simple:
      /* Normal mutex.  */
      LLL_MUTEX_LOCK (mutex);
      assert (mutex->__data.__owner == 0);
    }
  else if (__builtin_expect (PTHREAD_MUTEX_TYPE (mutex)
                             == PTHREAD_MUTEX_RECURSIVE_NP, 1))//可重入
    {
      /* Recursive mutex.  */
      pid_t id = THREAD_GETMEM (THREAD_SELF, tid);
      /* Check whether we already hold the mutex.  */
      if (mutex->__data.__owner == id)
        {
          /* Just bump the counter.  */
          if (__glibc_unlikely (mutex->__data.__count + 1 == 0))
            /* Overflow of the counter.  */
            return EAGAIN;
          ++mutex->__data.__count; /* 若已经持有了此锁, 增加计数, 无需block此线程 */
          return 0;
        }
      /* We have to get the mutex.  */
      LLL_MUTEX_LOCK (mutex);// 去判断锁变量, 如果不行, 被OS休眠掉
      assert (mutex->__data.__owner == 0);
      mutex->__data.__count = 1;// 拿到了锁, 锁变量是ok的,则设置count
    }
  else if (__builtin_expect (PTHREAD_MUTEX_TYPE (mutex)
                          == PTHREAD_MUTEX_ADAPTIVE_NP, 1))
    {
      if (! __is_smp)
        goto simple;
      if (LLL_MUTEX_TRYLOCK (mutex) != 0)
        {
          int cnt = 0;
          int max_cnt = MIN (max_adaptive_count (),
                             mutex->__data.__spins * 2 + 10);
          do
            {
              if (cnt++ >= max_cnt)
                {
                  LLL_MUTEX_LOCK (mutex);
                  break;
                }
              atomic_spin_nop ();
            }
          while (LLL_MUTEX_TRYLOCK (mutex) != 0);
          mutex->__data.__spins += (cnt - mutex->__data.__spins) / 8;
        }
      assert (mutex->__data.__owner == 0);
    }
  else
    {
      pid_t id = THREAD_GETMEM (THREAD_SELF, tid);
      assert (PTHREAD_MUTEX_TYPE (mutex) == PTHREAD_MUTEX_ERRORCHECK_NP);
      /* Check whether we already hold the mutex.  */
      if (__glibc_unlikely (mutex->__data.__owner == id))
        return EDEADLK;
      goto simple;
    }
  pid_t id = THREAD_GETMEM (THREAD_SELF, tid);
  /* Record the ownership.  */
  mutex->__data.__owner = id;

  LIBC_PROBE (mutex_acquired, 1, mutex);
  return 0;
}

2

pthread_mutex_unlock (pthread_mutex_t *mutex) {
    if (type == PTHREAD_MUTEX_TIMED_NP) {
        mutex->__data.__owner = 0;
        lll_unlock (mutex->__data.__lock, PTHREAD_MUTEX_PSHARED (mutex));
        return 0;
  }
    else {
         if (type == PTHREAD_MUTEX_RECURSIVE_NP) 
        -------------------
    }

  pthread_mutex_lock/unlock主要是调用底层的lll_lock/lll_unlock, 其实就是调用futex的FUTEX_WAIT/FUTEX_WAKE操作, 来实现线程的休眠和唤醒工作

# define LLL_MUTEX_LOCK(mutex) 
  lll_lock ((mutex)->__data.__lock, PTHREAD_MUTEX_PSHARED (mutex))

define __lll_lock(futex, private)                                      
  ((void)                                                               
   ({                                                                   
     int *__futex = (futex);                                            
     if (__glibc_unlikely                                               
         (atomic_compare_and_exchange_bool_acq (__futex, 1, 0)))        
       {                                                                
         if (__builtin_constant_p (private) && (private) == LLL_PRIVATE) 
           __lll_lock_wait_private (__futex);                           
         else                                                           
           __lll_lock_wait (__futex, private);                          
       }                                                                
   }))

atomic_compare_and_exchange_bool_acq 用于将_futex从0原子变为1,成功则返回0,从而获得锁退出。

失败则返回值>0(对应我们这里是1或者2),然后继续走分支。

futex变量的值有3种:0代表当前锁空闲,1代表有线程持有当前锁,2代表存在锁冲突。

  futex的值初始化时是0;当调用mutex-lock的时候会利用cas操作改为1;

  当调用lll_lock时,如果不存在锁冲突,则将其改为1,否则改为2。

根据值, 走__lll_lock_wait:

/* Note that we need no lock prefix.  */
#define atomic_exchange_acq(mem, newvalue) 
  ({ __typeof (*mem) result;                              
     if (sizeof (*mem) == 1)                              
       __asm __volatile ("xchgb %b0, %1"                      
             : "=q" (result), "=m" (*mem)                  
             : "0" (newvalue), "m" (*mem));                  
     else if (sizeof (*mem) == 2)                          
       __asm __volatile ("xchgw %w0, %1"                      
             : "=r" (result), "=m" (*mem)                  
             : "0" (newvalue), "m" (*mem));                  
     else if (sizeof (*mem) == 4)                          
       __asm __volatile ("xchgl %0, %1"                          
             : "=r" (result), "=m" (*mem)                  
             : "0" (newvalue), "m" (*mem));                  
     else                                      
       __asm __volatile ("xchgq %q0, %1"                      
             : "=r" (result), "=m" (*mem)                  
             : "0" ((atomic64_t) cast_to_integer (newvalue)),     
               "m" (*mem));                          
     result; })
void
__lll_lock_wait (int *futex, int private)
{
  if (*futex == 2)
    lll_futex_wait (futex, 2, private); /* Wait if *futex == 2.  */
 
  while (atomic_exchange_acq (futex, 2) != 0)
    lll_futex_wait (futex, 2, private); /* Wait if *futex == 2.  */
}

  同时附上老版本的ll_lock实现:

/*
 * CAS操作的核心宏,cas操作判断(mutex)->__data.__lock的值是否为0,如果为0,则置为1,ZF=1
 * 1.判断是否在多线程环境下
 * 2.如果不是多线程环境则直接调用cmpxchgl指令进行cas操作,如果是多线程则需要在cmpxchgl指令前
 * 加上lock指令
 * 3.如果cas成功则跳到标号18,如果cas失败则调用__lll_lock_wait子程序
 */
# define __lll_lock_asm_start "cmpl $0, %%gs:%P6
	"                 
                  "je 0f
	"                     
                  "lock
"                             //!!!!!!lock 指令!!!!!!!!!!!
                  "0:	cmpxchgl %1, %2
	"
//LLL_PRIVATE为0,所以不会走第一个分支,走第二个分支
#define lll_lock(futex, private) 
  (void)                                      
    ({ int ignore1, ignore2;                              
       if (__builtin_constant_p (private) && (private) == LLL_PRIVATE)        
     __asm __volatile (__lll_lock_asm_start                   
               "jz 18f
	"                   
               "1:	leal %2, %%ecx
"                 
               "2:	call __lll_lock_wait_private
"           
               "18:"                          
               : "=a" (ignore1), "=c" (ignore2), "=m" (futex)     
               : "0" (0), "1" (1), "m" (futex),           
                 "i" (MULTIPLE_THREADS_OFFSET)            
               : "memory");                         //!!!!!!指令影响内存!!!!!!!!!!!
       else                                   
     {                                    
       int ignore3;                               
       __asm __volatile (__lll_lock_asm_start                 
                 "jz 18f
	"                     
                 "1:	leal %2, %%edx
"               
                 "0:	movl %8, %%ecx
"               
                 "2:	call __lll_lock_wait
"             
                 "18:"                        
                 : "=a" (ignore1), "=c" (ignore2),            
                   "=m" (futex), "=&d" (ignore3)              
                 : "1" (1), "m" (futex),                  
                   "i" (MULTIPLE_THREADS_OFFSET), "0" (0),        
                   "g" ((int) (private))                  
                 : "memory");                     
     }                                    
    })
View Code

可知:xchgb 是一个原子操作在多核cpu 下也是的!!多核/多线程则需要在cmpxchgl指令前 加上lock指令 锁住地址总线 防止被修改:
具体分析可见:smp-volatile

  futex(&__lock)的值从0原子变为2就成功。否则调用lll_futex_wait,阻塞。这里的atomic_exchange_acq是一个返回旧值的原子操作,直接采用了内敛汇编(xchg)的方式,并且根据变量类型从而选取linux下不同的汇编指令。

到了这里,只要这个原子xchg的是正确的,并且阻塞与唤醒(wake up)之间的协议是正确的,那么这个mutex的语义就得到保证

 /* Wait while *FUTEXP == VAL for an lll_futex_wake call on FUTEXP.  */
 #define lll_futex_wait(futexp, val, private) 
  lll_futex_timed_wait (futexp, val, NULL, private)
 #define lll_futex_timed_wait(futexp, val, timeout, private)     
   lll_futex_syscall (4, futexp,                                 
              __lll_private_flag (FUTEX_WAIT, private),  
              val, timeout)

glibc/nptl/lowlevellock.c 文件以及其头文件

所以:lll_futex_syscall调用简化为

lll_futex_syscall (4, futexp,   0,     2,   NULL)

1.根据锁类型进行对应的操作,如果是普通锁PTHREAD_MUTEX_TIMED_NP则进行CAS操作,如果CAS失败则进行sys_futex系统调用挂起当前线程

2.如果是递归锁PTHREAD_MUTEX_RECURSIVE_NP,则判断当前线程id是否和持有锁的线程id是否相等,如果相等说明是重入的,则将加锁次数加一。否则进行CAS操作获取锁,如果CAS失败则进行sys_futex系统调用挂起当前线程。

3.如果是适配/自适应 锁PTHREAD_MUTEX_ADAPTIVE_NP,在获取锁的时候会进行自旋操作,当自旋的次数超过最大值时,则进行sys_futex系统调用挂起当前线程。

4.如果是检错锁PTHREAD_MUTEX_ERRORCHECK_NP,则判断锁是否已经被当前线程获取,如果是则返回错误,否则进行CAS操作获取锁,检错锁可以避免普通锁出现的死锁情况。

在获取锁失败后,会将互斥量设置为2,然后进行系统调用进行挂起,这是为了让解锁线程发现有其它等待互斥量的线程需要被唤醒

pthread_mutex_unlock 

其核心如下:

 #define __lll_unlock(futex, private)  
 ((void) ({   int *__futex = (futex);   
 int __val = atomic_exchange_rel (__futex, 0);     
 if (__builtin_expect (__val > 1, 0))  
 lll_futex_wake (__futex, 1, private);   })) 
 
 #define lll_unlock(futex, private) __lll_unlock(&(futex), private) 
 
 #define lll_futex_wake(ftx, nr, private)  ({  
 DO_INLINE_SYSCALL(futex, 3, (long) (ftx), 
 __lll_private_flag (FUTEX_WAKE, private),   
 (int) (nr));   _r10 == -1 ? -_retval : _retval;  }) 

lll_unlock分为两个步骤:

  1. 将futex设置为0并拿到设置之前的值(用户态操作)
  2. 如果futex之前的值>1,代表存在锁冲突,也就是说有线程调用了FUTEX_WAIT在休眠,所以通过调用系统函数FUTEX_WAKE唤醒休眠线程

futex变量的值有3种:0代表当前锁空闲,1代表有线程持有当前锁,2代表存在锁冲突。

  futex的值初始化时是0;当调用mutex-lock的时候会利用cas操作改为1;

  当调用lll_lock时,如果不存在锁冲突,则将其改为1,否则改为2。

对比就知道为什么futex>1  就需要 futex_wake了

/**
 * futex_wait_setup() - 准备等待在一个futex变量上
 * @uaddr: futex用户空间地址
 * @val: 期望的值
 * @flags: futex标识 (FLAGS_SHARED, etc.)
 * @q:  关联的futex_q
 * @hb:  hash_bucket的指针 返回给调用者
 *
 * Setup the futex_q and locate the hash_bucket.  Get the futex value and
 * compare it with the expected value.  Handle atomic faults internally.
 * Return with the hb lock held and a q.key reference on success, and unlocked
 * with no q.key reference on failure.
 *
 * Return:
 *  0 - uaddr contains val and hb has been locked;
 * <1 - -EFAULT or -EWOULDBLOCK (uaddr does not contain val) and hb is unlocked
 */
static int futex_wait_setup(u32 __user *uaddr, u32 val, unsigned int flags,
      struct futex_q *q, struct futex_hash_bucket **hb)
{
 u32 uval;
 int ret;

 /*
  * Access the page AFTER the hash-bucket is locked.
  * Order is important:
  *
  *   Userspace waiter: val = var; if (cond(val)) futex_wait(&var, val);
  *   Userspace waker:  if (cond(var)) { var = new; futex_wake(&var); }
  *
  * The basic logical guarantee of a futex is that it blocks ONLY
  * if cond(var) is known to be true at the time of blocking, for
  * any cond.  If we locked the hash-bucket after testing *uaddr, that
  * would open a race condition where we could block indefinitely with
  * cond(var) false, which would violate the guarantee.
  *
  * On the other hand, we insert q and release the hash-bucket only
  * after testing *uaddr.  This guarantees that futex_wait() will NOT
  * absorb a wakeup if *uaddr does not match the desired values
  * while the syscall executes.
  */
retry:
   //初始化futex_q 填充q->key
 ret = get_futex_key(uaddr, flags & FLAGS_SHARED, &q->key, VERIFY_READ);
 if (unlikely(ret != 0))
  return ret;

retry_private:
  //1.对q->key进行hash 然后通过&futex_queues[hash & ((1 << FUTEX_HASHBITS)-1)]找到futex_hash_bucket
  //2. spin_lock(&hb->lock)获得自旋锁
 *hb = queue_lock(q);
 //原子的将uaddr的值拷贝到uval中
 ret = get_futex_value_locked(&uval, uaddr);
 if (ret) { //拷贝操作失败 重试
    ...
  goto retry;
 }
 // 如果uval的值(即uaddr)不等于期望值val 则表明其他线程在修改
  // 直接返回无需等待
 if (uval != val) {
  queue_unlock(q, *hb);
  ret = -EWOULDBLOCK;
 }

out:
 if (ret)
  put_futex_key(&q->key);
 return ret;
}

futex_wait_setup()方法主要是为阻塞在uaddr上做准备(uaddr 地址 为pthread_mutex_t  结构中lock值对应的地址),主要步骤:

  1. 初始化futex_q,并初始化futex_q.key的引用,
  2. futex_q.key进行hash通过&futex_queues[hash & ((1 << FUTEX_HASHBITS)-1)]找到futex_hash_bucket
  3. 调用spin_lock(&hb->lock)尝试获得自旋锁 失败则进行重试回到步骤1
  4. 判断*uaddr的值跟val是否相等,不相等说明其他线程在修改则释放持有的hb.lock自旋锁,返回-EWOULDBLOCK(#define EWOULDBLOCK 246 /* Operation would block (Linux returns EAGAIN) */即linux中的EAGAIN)
**
 * futex_wait_queue_me() - queue_me() and wait for wakeup, timeout, or signal
 * @hb:  the futex hash bucket, must be locked by the caller
 * @q:  the futex_q to queue up on
 * @timeout: the prepared hrtimer_sleeper, or null for no timeout
 */
static void futex_wait_queue_me(struct futex_hash_bucket *hb, struct futex_q *q,
    struct hrtimer_sleeper *timeout)
{
  // task状态保证在另一个task唤醒它之前被设置,set_current_state利用set_mb()实现
  // 设置task状态为TASK_INTERRUPTIBLE CPU只会调度状态为TASK_RUNNING的任务
 set_current_state(TASK_INTERRUPTIBLE);
  //将q插入到等待队列中然后释放自旋锁
 queue_me(q, hb);
 //启动定时器
 if (timeout) {
  hrtimer_start_expires(&timeout->timer, HRTIMER_MODE_ABS);
  if (!hrtimer_active(&timeout->timer))
   timeout->task = NULL;
 }

 /*
  * If we have been removed from the hash list, then another task
  * has tried to wake us, and we can skip the call to schedule().
  */
 if (likely(!plist_node_empty(&q->list))) {
  /*
   * If the timer has already expired, current will already be
   * flagged for rescheduling. Only call schedule if there
   * is no timeout, or if it has yet to expire.
   */
    // 未设置过期时间 或过期时间还未到期才进行调度
  if (!timeout || timeout->task)
      //系统重新进行调度,此时CPU会去执行其他任务,当前任务会被阻塞
   schedule();
 }
  // 走到这里说明当前任务被CPU选中执行
 __set_current_state(TASK_RUNNING);
}

futex_wait_queue_me主要做了几件事:

  1. 将设置task状态为TASK_INTERRUPTIBLE (CPU只会调度状态为TASK_RUNNING的任务)
  2. 调用queueme()futex_q插入到等待队列
  3. 启动定时任务
  4. 若未设置过期时间或过期时间未到期 重新调度进程
  5. 获的执行资格 设置task状态为TASK_RUNNING

  总结一下futex_wait的工作机制:futex_wait_setup()方法会根据futex_q.keyhash找到对应futex_hash_bucket,并通过spin_lock(futex_hash_bucket.lock)获取自旋锁;futex_wait_queue_me()方法先是将task设置为TASK_INTERRUPTIBLE然后调用queue_me()futex_q入队,然后才spin_unlock(&hb->lock);释放持有的自旋锁。也就是说检查*uaddr值和进程/线程挂起放在了一个临界区中,保证了条件和等待之间的原子性。

以上信息来自:https://jishuin.proginn.com/p/763bfbd55ad5

futex_wake

futex_wake()唤醒阻塞在*uaddr上的任务

static int
futex_wake(u32 __user *uaddr, unsigned int flags, int nr_wake, u32 bitset)
{
 struct futex_hash_bucket *hb;
 struct futex_q *this, *next;
 struct plist_head *head;
 union futex_key key = FUTEX_KEY_INIT;
 int ret;

 if (!bitset)
  return -EINVAL;
  //根据uaddr的值填充&key的内容
 ret = get_futex_key(uaddr, flags & FLAGS_SHARED, &key, VERIFY_READ);
 if (unlikely(ret != 0))
  goto out;
 //根据&key获取对应uaddr所在的futex_hash_bucket
 hb = hash_futex(&key);
  //对该hb加自旋锁
 spin_lock(&hb->lock);
 head = &hb->chain;
 //遍历该hb的链表 注意链表中存储的节点时plist_node类型,而这里的this却是futex_q类型
  //这种类型转换是通过c中的container_of机制实现的
 plist_for_each_entry_safe(this, next, head, list) {
  if (match_futex (&this->key, &key)) {
   if (this->pi_state || this->rt_waiter) {
    ret = -EINVAL;
    break;
   }

   /* Check if one of the bits is set in both bitsets */
   if (!(this->bitset & bitset))
    continue;
   //唤醒对应进程
   wake_futex(this);
   if (++ret >= nr_wake)
    break;
  }
 }
 //释放自旋锁
 spin_unlock(&hb->lock);
 put_futex_key(&key);
out:
 return ret;
}
static void wake_futex(struct futex_q *q)
{
 struct task_struct *p = q->task;

 if (WARN(q->pi_state || q->rt_waiter, "refusing to wake PI futex
"))
  return;

  // 在唤醒任务之前将q->lock_ptr设置为NULL
  // 如果另一个CPU发生了非futex方式的唤醒,则任务可能退出。p解引用则是一个不存在的task结构体
  // 为避免这种case,需要持有p引用来进行唤醒
 get_task_struct(p);
 //将q出队列
 __unqueue_futex(q);
 /*
  * 只要写入q-> lock_ptr = NULL,等待的任务就可以释放futex_q,而无需获取任何锁。
   * 这里需要一个内存屏障,以防止lock_ptr的后续存储超越plist_del。
  */
 smp_wmb();
 q->lock_ptr = NULL;
 // 将TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE状态的task唤醒
 wake_up_state(p, TASK_NORMAL);
  // task释放futex_q
 put_task_struct(p);
}

futex_wake()流程如下:

  1. 根据*uaddr填充futex_q->key,调用hash_futex(&key);查找对应的futex_hash_bucket
  2. 调用spin_lock(&hb->lock);尝试获取hb的自旋锁
  3. 遍历futex_hash_bucket挂载的链表,找到uaddr对应的节点
  4. 调用wake_futex唤起等待的进程,其实就是将task设置为TASK_RUNNING并放入调度队列中等待CPU调度同时释放futex_q
  5. 释放自旋锁

此外futex同步机制即可用于线程之间同步,也可用于进程之间同步。

  • 用于线程比较简单,因为线程共享虚拟内存空间,futex变量由唯一的虚拟地址表示,线程即可用虚拟地址访问futex变量
  • 用于进程稍微复杂一些,因为进程间是分配的独立的虚拟内存地址空间。需要通过mmap让进程可以共享一段地址空间来使用futex变量。故而每个进程访问futex的虚拟地址不一样,但是操作系统知道所有这些虚拟地址映射到同一个表示futex变量的物理地址

思考一下:

  在目前的mutex实现前提下, 用lock-free一般不会更快. 它们都只能同时让一个线程做有用的事, 在性能上不会有质的区别. 由于lock-free的代码会明显复杂, 性能还未必更高. 所以除非场景特殊, 用lock-free并不是个好主意.
更好的方法是减少数据共享, 比如你这里分多个桶加锁就行了, 性能会比单纯用一套lock-free的数据结构更好. 在追求极限性能的场景中,一般考虑的是wait-free的算法和容器, 这能让所有线程同时做有用的事, 在关键代码上相比加锁和lock-free才有较大的区别.

来自:https://www.zhihu.com/question/53303879
 
http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!! 但行好事 莫问前程 --身高体重180的胖子
原文地址:https://www.cnblogs.com/codestack/p/14692940.html