最近开始学习无锁编程,和传统的基于Lock的算法相比,无锁编程具有其独特的优点,Angel Lucifer的关于无锁编程一文对此有详细的描述。

无锁编程的目标是在不使用Lock的前提下保证并发过程中共享数据的一致性,其主要的实现基础是CAS操作,也就是compare_and_swap,通过处理器提供的指令,可以原子地更新共享数据,并同时监测其他线程的干扰,.Net中的对应实现是InterLocked.CompareExchange函数。

既然不使用Lock,那在无锁编程中要时刻注意的是,代码可能在任意语句中被中断。如果是单个变量,我们可以使用 InterLocked.XXX 保证操作的原子性,但是如果有多个操作要完成的话,简单地组合 InterLocked.XXX 是远远不够的。通常的原则是对函数中用到的共享变量,先在代码开始处用局部变量保存它的内容,在后面更新共享变量时,使用前述变量来判断其是否发生了改变,如果共享变量发生了改变,那么我们可能需要重试,或者在某些可能的情况下,当前线程可以"帮助"其他更新中的线程完成更新。

从上面可以总结出无锁算法的两个基本特征:

1. 无锁算法总是包含一个循环结构,以保证更新失败后重试

2. 无锁算法在更新共享变量时,总是使用CAS和原始值进行比较,以保证没有冲突

基于无锁的C#并发队列实现_无锁

 1基于无锁的C#并发队列实现_共享数据_02public class ConcurrentLinkedQueue<T> 

 2基于无锁的C#并发队列实现_无锁{

 3基于无锁的C#并发队列实现_无锁编程_04    private class Node<K>

 4基于无锁的C#并发队列实现_无锁编程_05    {

 5基于无锁的C#并发队列实现_无锁编程_04        internal K Item;

 6基于无锁的C#并发队列实现_无锁编程_04        internal Node<K> Next;

 7基于无锁的C#并发队列实现_无锁编程_04

 8基于无锁的C#并发队列实现_无锁编程_04        public Node(K item, Node<K> next)

 9基于无锁的C#并发队列实现_无锁编程_05        {

10基于无锁的C#并发队列实现_无锁编程_04            this.Item = item;

11基于无锁的C#并发队列实现_无锁编程_04            this.Next = next;

12基于无锁的C#并发队列实现_.net_13        }

13基于无锁的C#并发队列实现_.net_13    }

14基于无锁的C#并发队列实现_无锁编程_04

15基于无锁的C#并发队列实现_无锁编程_04    private Node<T> _head;

16基于无锁的C#并发队列实现_无锁编程_04    private Node<T> _tail;

17基于无锁的C#并发队列实现_无锁编程_04

18基于无锁的C#并发队列实现_无锁编程_04    public ConcurrentLinkedQueue()

19基于无锁的C#并发队列实现_无锁编程_05    {

20基于无锁的C#并发队列实现_无锁编程_04        _head = new Node<T>(default(T), null);

21基于无锁的C#并发队列实现_无锁编程_04        _tail = _head;

22基于无锁的C#并发队列实现_.net_13    }

23基于无锁的C#并发队列实现_无锁编程_04

24基于无锁的C#并发队列实现_无锁编程_04    public bool IsEmpty

25基于无锁的C#并发队列实现_无锁编程_05    {

26基于无锁的C#并发队列实现_无锁编程_05        get { return (_head.Next == null); }

27基于无锁的C#并发队列实现_.net_13    }

28基于无锁的C#并发队列实现_无锁编程_04

29基于无锁的C#并发队列实现_无锁编程_04    public void Enqueue(T item)

30基于无锁的C#并发队列实现_无锁编程_05    {

31基于无锁的C#并发队列实现_无锁编程_04        Node<T> newNode = new Node<T>(item, null);

32基于无锁的C#并发队列实现_无锁编程_04        while (true)

33基于无锁的C#并发队列实现_无锁编程_05        {

34基于无锁的C#并发队列实现_无锁编程_04            Node<T> curTail = _tail;

35基于无锁的C#并发队列实现_无锁编程_04            Node<T> residue = curTail.Next;

36基于无锁的C#并发队列实现_无锁编程_04

37基于无锁的C#并发队列实现_无锁编程_04            //判断_tail是否被其他process改变

38基于无锁的C#并发队列实现_无锁编程_04            if (curTail == _tail)

39基于无锁的C#并发队列实现_无锁编程_05            {

40基于无锁的C#并发队列实现_无锁编程_04                //A 有其他process执行C成功,_tail应该指向新的节点

41基于无锁的C#并发队列实现_无锁编程_04                if (residue == null) 

42基于无锁的C#并发队列实现_无锁编程_05                {

43基于无锁的C#并发队列实现_无锁编程_04                    //C 如果其他process改变了tail.next节点,需要重新取新的tail节点

44基于无锁的C#并发队列实现_无锁编程_04                    if (Interlocked.CompareExchange<Node<T>>(

45基于无锁的C#并发队列实现_无锁编程_04                        ref curTail.Next, newNode, residue) == residue) 

46基于无锁的C#并发队列实现_无锁编程_05                    {

47基于无锁的C#并发队列实现_无锁编程_04                        //D 尝试修改tail

48基于无锁的C#并发队列实现_无锁编程_04                        Interlocked.CompareExchange<Node<T>>(ref _tail, newNode, curTail); 

49基于无锁的C#并发队列实现_无锁编程_04                        return;

50基于无锁的C#并发队列实现_.net_13                    }

51基于无锁的C#并发队列实现_.net_13                }

52基于无锁的C#并发队列实现_无锁编程_04                else

53基于无锁的C#并发队列实现_无锁编程_05                {

54基于无锁的C#并发队列实现_无锁编程_04                    //B 帮助其他线程完成D操作

55基于无锁的C#并发队列实现_无锁编程_04                    Interlocked.CompareExchange<Node<T>>(ref _tail, residue, curTail); 

56基于无锁的C#并发队列实现_.net_13                }

57基于无锁的C#并发队列实现_.net_13            }

58基于无锁的C#并发队列实现_.net_13        }

59基于无锁的C#并发队列实现_.net_13    }

60基于无锁的C#并发队列实现_无锁编程_04

61基于无锁的C#并发队列实现_无锁编程_04    public bool TryDequeue(out T result)

62基于无锁的C#并发队列实现_无锁编程_05    {

63基于无锁的C#并发队列实现_无锁编程_04        Node<T> curHead;

64基于无锁的C#并发队列实现_无锁编程_04        Node<T> curTail;

65基于无锁的C#并发队列实现_无锁编程_04        Node<T> next;

66基于无锁的C#并发队列实现_无锁编程_04        do

67基于无锁的C#并发队列实现_无锁编程_05        {

68基于无锁的C#并发队列实现_无锁编程_04            curHead = _head;

69基于无锁的C#并发队列实现_无锁编程_04            curTail = _tail;

70基于无锁的C#并发队列实现_无锁编程_04            next = curHead.Next;

71基于无锁的C#并发队列实现_无锁编程_04            if (curHead == _head)

72基于无锁的C#并发队列实现_无锁编程_05            {

73基于无锁的C#并发队列实现_无锁编程_04                if (next == null)  //Queue为空

74基于无锁的C#并发队列实现_无锁编程_05                {

75基于无锁的C#并发队列实现_无锁编程_04                    result = default(T);

76基于无锁的C#并发队列实现_无锁编程_04                    return false;

77基于无锁的C#并发队列实现_.net_13                }

78基于无锁的C#并发队列实现_无锁编程_04                if (curHead == curTail) //Queue处于Enqueue第一个node的过程中

79基于无锁的C#并发队列实现_无锁编程_05                {

80基于无锁的C#并发队列实现_无锁编程_04                    //尝试帮助其他Process完成操作

81基于无锁的C#并发队列实现_无锁编程_04                    Interlocked.CompareExchange<Node<T>>(ref _tail, next, curTail); 

82基于无锁的C#并发队列实现_.net_13                }

83基于无锁的C#并发队列实现_无锁编程_04                else

84基于无锁的C#并发队列实现_无锁编程_05                {

85基于无锁的C#并发队列实现_无锁编程_04                    //取next.Item必须放到CAS之前

86基于无锁的C#并发队列实现_无锁编程_04                    result = next.Item; 

87基于无锁的C#并发队列实现_无锁编程_04                    //如果_head没有发生改变,则将_head指向next并退出

88基于无锁的C#并发队列实现_无锁编程_04                    if (Interlocked.CompareExchange<Node<T>>(ref _head, 

89基于无锁的C#并发队列实现_无锁编程_04                        next, curHead) == curHead)

90基于无锁的C#并发队列实现_无锁编程_04                        break;

91基于无锁的C#并发队列实现_.net_13                }

92基于无锁的C#并发队列实现_.net_13            }

93基于无锁的C#并发队列实现_.net_13        }

94基于无锁的C#并发队列实现_无锁编程_04        while (true);

95基于无锁的C#并发队列实现_无锁编程_04        return true;

96基于无锁的C#并发队列实现_.net_13    }

97基于无锁的C#并发队列实现_无锁编程_98}

98基于无锁的C#并发队列实现_共享数据_02

根据自己的测试(双核CPU),在轻度和中度争用情况下,无锁算法比基于锁的算法性能好很多,在争用非常严重的情况下(100个并发线程以上/每CPU),基于锁的算法性能开始显示出优势,因为一旦发生争用,基于锁的算法会立刻切换到其他线程,而无锁算法会进入下一次循环,导致CPU的占用。但是如此严重的争用在实际中并不多见,并且可以采用SpinWait的方法加以改进。基于锁的算法在测试中曾经出现过类似死锁的现象,无锁算法则完全没有出过类似问题,另外,处理器核心越多,基于锁的算法效率越差。

 

从上面的算法实现中,可以体会到无锁算法的优势:在并发的多个线程中,总是有线程能够推进,算法总能在有限的循环次数内完成,并且在某些冲突的情况下,一个线程可以“帮助”其他线程完成被中断的工作,这些对提高吞吐量都有很大的作用。