Notes on Locks

Spinlock 

  an non-scalable implementation in C++

# if defined __GNUC__
# include <pthread.h> class Spinlock { # if (__GNUC__ >= 4) private: pthread_spinlock_t _spinlock; public: Spinlock() { pthread_spin_init(&_spinlock, PTHREAD_PROCESS_PRIVATE); } ~Spinlock() { pthread_spin_destroy(&_spinlock); } void lock() { pthread_spin_lock(&_spinlock); } void unlock() { pthread_spin_unlock(&_spinlock); } # else private: volatile int _inter_lock; public: Spinlock() : _inter_lock(0) { } void lock() { register int temp; __asm__ __volatile__ ( "1: \n" " cmp $0, %0 \n" // cmp(0, _inter_lock) " je 2f \n" // if _inter_lock == 0, goto 2 " pause \n" " jmp 1b \n" // goto 1 "2: \n" " movl $1, %1 \n" // temp = 1 " xchg %0, %1 \n" // exchange(_inter_lock, temp) " test %1, %1 \n" // test(temp, temp), do AND " jne 1b \n" // if temp == 1, goto 1 : "=m"(_inter_lock), "=r"(temp) ); // A note on 'pause': // 'pause' gives hint to processor that improves performance // of spin-wait loops } void unlock() { // _inter_lock = 0; __asm__ __volatile__ ( " movl $0, %0 \n" : "=m"(_inter_lock) ); } # endif // __GNUC__ >= 4 private: // non-copyable Spinlock(const Spinlock&); Spinlock& operator=(const Spinlock&); }; # endif // __GNUC__

 

Pseudocode for ticket locks in Linux    (The ticket lock is the default lock since kernel version 2.6.25 , released in April 2008)

struct spinlock_t {
    int current_ticket; 
    int next_ticket; 
};

void spin_lock (spinlock_t *lock) { 
    int t = atomic_fetch_and_inc(&lock->next_ticket);
    while (t != lock->current_ticket) 
        ; /* spin */ 
} 

void spin_unlock (spinlock_t *lock) { 
    lock->current_ticket++; 
}

 

MCS spinlock (scalable, for implementing Linux mutex) 

//  atomic exchange:  
//    *ptr, val = val, *ptr
//    return val
static inline long xchg(long *ptr, long val)
{
    __asm__ volatile(
        "lock; xchgq %0, %1\n\t"
        : "+m" (*ptr), "+r" (val)
        :
        : "memory", "cc");
    return val;
}

// cmpxchg:
//     if (*ptr == old) {
//         *ptr = new;
//         return old;
//     } else 
//         return *ptr   
static inline long cmpxchg(long *ptr, long old, long val)
{
    uint64_t out;
    __asm__ volatile(
        "lock; cmpxchgq %2, %1"
        : "=a" (out), "+m" (*ptr)
        : "q" (val), "0"(old)
        : "memory");

    return out;
}

struct qnode {
    volatile void *next;
    volatile char locked; // 1 if lock acquired
    char __pad[0] __attribute__((aligned(CACHELINE)));
};


typedef struct {
    // tail of queue of threads holding or waiting the lock
    struct qnode *tail  __attribute__((aligned(64)));
    int lock_idx  __attribute__((aligned(64)));
} mcslock_t;


// initialize main lock
static inline void
mcs_init(mcslock_t *l)
{
    l->tail = NULL;
}

static inline void
mcs_lock(mcslock_t *l, volatile struct qnode *mynode)
{
    struct qnode *predecessor; 

    mynode->next = NULL;
    predecessor = (struct qnode *)xchg((long *)&l->tail, (long)mynode);

    if (predecessor) {
        mynode->locked = 1; // mark the lock as taken
        asm volatile("":::"memory")  // barrier
        predecessor->next = mynode; 
        while (mynode->locked) // busy waiting
            nop_pause();
    }
}

static inline void
mcs_unlock(mcslock_t *l, volatile struct qnode *mynode)
{
    if (!mynode->next) { // if no cores waiting
        // atomic lock-free pop: 
        //     if we are the only node in the queue, reset l->tail and return
        if (cmpxchg((long *)&l->tail, (long)mynode, 0) == (long)mynode)
            return;
        while (!mynode->next)
            nop_pause();
    }
    ((struct qnode *)mynode->next)->locked = 0; // handoff lock to the successor
}

 

For scalable locks

  See:

          Non-scalable locks are dangerous    

          my notes on scalable spinlocks 

 

Mutex

        Linux kernel bug: A surprise with mutexes and reference counts

        Russ Cox on debugging an interesting concurrency bug    

   adaptive mutex: 

   

posted @ 2016-09-17 14:28  william-cheung  阅读(229)  评论(0编辑  收藏  举报