传送门:异步编程系列目录……

最近在学习.NET4.5关于“并行任务”的使用。“并行任务”有自己的同步机制,没有显示给出类似如旧版本的:事件等待句柄、信号量、lock、ReaderWriterLock……等同步基元对象,但我们可以沿溪这一编程习惯,那么这系列翻译就是给“并行任务”封装同步基元对象。翻译资源来源《(译)关于Async与Await的FAQ》

1.         构建Async同步基元,Part 1 AsyncManualResetEvent

2.         构建Async同步基元,Part 2 AsyncAutoResetEvent

3.         构建Async同步基元,Part 3 AsyncCountdownEvent

4.         构建Async同步基元,Part 4 AsyncBarrier

5.         构建Async同步基元,Part 5 AsyncSemaphore

6.         构建Async同步基元,Part 6 AsyncLock

7.         构建Async同步基元,Part 7 AsyncReaderWriterLock

源码:构建Async同步基元.rar

开始:构建Async同步基元,Part 7 AsyncReaderWriterLock

在上一篇文章中,我们构建了一个AsyncLock,本文,我将构建一个更高级的构造,一个异步版本的读写/锁。

异步版本的读写/锁比前几篇文章中所创建的同步基元要更加复杂。它包含更多的控制,这意味着需要更多决定来构建这个类型的精确行为。对于本例,我做了以下决定。首先,写操作比读操作拥有更高的优先级,即无论读操作或写操作请求的顺序,如果有写操作正在等待,它将得到优先于任何数量的读操作得到处理,即使它比那些读操作请求的晚些。第二,我决定不对读操作限流,即只要此时不存在未解决的写操作或等待着的写操作,所有读操作会立马被唤醒。

这是我们将构建的目标类型:

1
2
3
4
5
6
7
8
9
10
11
12
public class AsyncReaderWriterLock
{
    public AsyncReaderWriterLock();
 
    public Task<Releaser> ReaderLockAsync();
    public Task<Releaser> WriterLockAsync();
 
    public struct Releaser : IDisposable
    {
        public void Dispose();
    }
}

就像AsyncLock一样,我们使用一个实现IDiposable接口的Releaser结构,使AsyncReaderWriterLock类型能轻易用于一个范围,例如:

1
2
3
4
5
6
private readonly AsyncReaderWriterLock m_lock = new AsyncReaderWriterLock();
using(var releaser = await m_lock.ReaderLockAsync())
{
     // protected code here
}

现在构建Releaser结构,我们使用一个bool值类型变量标识是否是写操作,因为我们需要使用不同的方式来唤醒两种操作的锁(优先处理写操作)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public struct Releaser : IDisposable
{
    private readonly AsyncReaderWriterLock m_toRelease;
    private readonly bool m_writer;
 
    internal Releaser(AsyncReaderWriterLock toRelease, bool writer)
    {
        m_toRelease = toRelease;
        m_writer = writer;
    }
 
    public void Dispose()
    {
        if (m_toRelease != null)
        {
            if (m_writer)
                m_toRelease.WriterRelease();
            else
                m_toRelease.ReaderRelease();
        }
    }
}

因为AsyncReaderWriterLock比前几篇文章中所创建的同步基元要更加复杂,所以我需要更多成员变量。首先,我们为了提高性能,我为读操作的等待缓存了一个Task<Releaser>,同时也为写操作的等待缓存了一个Task<Releaser>以便立即完成。

1
2
3
4
5
6
7
private readonly Task<Releaser> m_readerReleaser;
private readonly Task<Releaser> m_writerReleaser;
public AsyncReaderWriterLock()
{
    m_readerReleaser = Task.FromResult(new Releaser(this, false));
    m_writerReleaser = Task.FromResult(new Releaser(this, true));
}

接下来,我需要为写操作维护一个等待队列,对于每一个写操作等待者都有对应的Task<CompletionSource>实例,因为我需要支持单独唤醒它们。同样也需要为读操作维护一个TaskCompletionSource<Releaser>实例。然而,对于读操作,按我们之前谈论的设计,当允许读操作运行时,就能让所有读操作一起运行,因此我只需要一个TaskCompletionSource<Releaser>实例。然而,因为我为读操作只维护了一个TaskCompletionSource<Releaser>实例,所以我还需要知道有多少个读操作正在等待,以便当我最终唤醒它们时,我能确保所有读操作被唤醒。

1
2
3
4
5
private readonly Queue<TaskCompletionSource<Releaser>> m_waitingWriters = 
                                 new Queue<TaskCompletionSource<Releaser>>();
private TaskCompletionSource<Releaser> m_waitingReader =
                                 new TaskCompletionSource<Releaser>();
private int m_readersWaiting;

最后,我需要一个变量来维护锁的当前状态,它是一个整型,数值为0表示锁空闲;数值为-1表示一个写操作获取锁;正值表示一个或多个读操作获取锁。

1
private int m_status;

现在我们来实现4个方法:ReaderLockAsync, ReaderRelease, WriterLockAsync, and WriterRelease。(为了保证这4个方法数据的同步,我们首先对m_waitingWriters加锁)

ReaderLockAsync()方法用于读操作获取锁。在对m_waitingWriters加独占锁后,我们需要决定读操作是应该立即响应还是应该被迫等待。正如前面的决策,如果当前不存在等待的写操作或正在执行写操作,则读操作能立即得到响应,这种情况下,我们递增m_status计数并且返回为读操作缓存的TaskCompletionSource<Releaser>实例。如果当前存在等待的写操作或正在执行写操作,则我们强迫读操作等待,我们递增m_readersWaiting计数并且为读操作缓存的TaskCompletionSource<Releaser>实例添加延续任务并返回(延续任务确保所有的等待者能并发运行而不是串行化)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Task<Releaser> ReaderLockAsync()
{
    lock (m_waitingWriters)
    {
        if (m_status >= 0 && m_waitingWriters.Count == 0)
        {
            ++m_status;
            return m_readerReleaser;
        }
        else
        {
            ++m_readersWaiting;
            return m_waitingReader.Task.ContinueWith(t => t.Result);
        }
    }
}

WriterLockAsync()用于写操作获取锁。就像ReaderLockAsync()方法一样,需要对m_waitingWriters加独占锁,并且要考虑两种情况:写操作要么立即被响应,要么被迫等待。只有当前锁是空闲状态写操作才能立即被响应,因为写操作不能和任意读操作或其他写操作同时运行。所以,如果m_status值为0,我们更改m_status为-1表示现在正在运行一个写操作,并且返回为写操作缓存的Queue<TaskCompletionSource<Releaser>>队列。否则,我们为写操作创建一个新的TaskCompletionSource<Releaser>实例插入到队列中并且返回此实例对应的Task。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Task<Releaser> WriterLockAsync()
{
    lock (m_waitingWriters)
    {
        if (m_status == 0)
        {
            m_status = -1;
            return m_writerReleaser;
        }
        else
        {
            var waiter = new TaskCompletionSource<Releaser>();
            m_waitingWriters.Enqueue(waiter);
            return waiter.Task;
        }
    }
}

现在我们来构建唤醒功能,这些功能在被激活的读操作或写操作完成工作并且想释放它们持有的锁时被调用。ReaderRelease()需要递减读操作激活数计数,并且检查当前锁的状态。如果此时唤醒的是最后一个激活的读操作并且存在写操作正在等待,则会唤醒一个写操作并且设置m_status值为-1标识写操作获得锁。我们不需要检查任何等待的读操作,因为写操作比任何数量的读操作优先得到处理。如果此时没有任何等待的写操作,则任一后续请求的读操作将立即被响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void ReaderRelease()
{
    TaskCompletionSource<Releaser> toWake = null;
 
    lock (m_waitingWriters)
    {
        --m_status;
        if (m_status == 0 && m_waitingWriters.Count > 0)
        {
            m_status = -1;
            toWake = m_waitingWriters.Dequeue();
        }
    }
 
    if (toWake != null)
        toWake.SetResult(new Releaser(this, true));
}

最后,我们构建WriterRelease()方法。当读操作完成时,如果等待队列中存在等待的写操作,我们从队列中取出一个写操作并且完成任务(因为将激活一个新的写操作,旧的写操作完成后,新的写操作会取代它的位置,所以不需要更新锁的状态m_status)。如果等待队列中不存在等待的写操作,但是此时存在等待的读操作,我们会唤醒所有读操作对应的任务,在这种情况下,我们还要为后续的读操作创建一个新的等待任务,并且需要更新m_status为当前激活的读操作数。如果没有任何读操作或写操作,则我们重置锁状态m_status。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private void WriterRelease()
{
    TaskCompletionSource<Releaser> toWake = null;
    bool toWakeIsWriter = false;
 
    lock (m_waitingWriters)
    {
        if (m_waitingWriters.Count > 0)
        {
            toWake = m_waitingWriters.Dequeue();
            toWakeIsWriter = true;
        }
        else if (m_readersWaiting > 0)
        {
            toWake = m_waitingReader;
            m_status = m_readersWaiting;
            m_readersWaiting = 0;
            m_waitingReader = new TaskCompletionSource<Releaser>();
        }
        else m_status = 0;
    }
 
    if (toWake != null)
        toWake.SetResult(new Releaser(this, toWakeIsWriter));
}

这就是本节要讲的AsyncReaderWriterLock。

完整源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/// <summary>
/// Async版的 read/writer 锁
/// </summary>
public class AsyncReaderWriterLock
{
    // 为了提高性能,我为读/写操作的等待各缓存了一个Task<Releaser>
    private readonly Task<Releaser> m_readerReleaser;
    private readonly Task<Releaser> m_writerReleaser;
    // 写操作的等待队列
    private readonly Queue<TaskCompletionSource<Releaser>> m_waitingWriters =
                                          new Queue<TaskCompletionSource<Releaser>>();
    // 读操作的等待任务
    private TaskCompletionSource<Releaser> m_waitingReader =
                                          new TaskCompletionSource<Releaser>();
    // 当前有多少个等待着的读操作计数
    private int m_readersWaiting;
    // 维护锁的当前状态。0表示锁空闲;-1表示一个写操作获取锁;正值表示一个或多个读操作获取锁。
    private int m_status;
    public AsyncReaderWriterLock()
    {
        m_readerReleaser = Task.FromResult(new Releaser(this, false));
        m_writerReleaser = Task.FromResult(new Releaser(this, true));
    }
 
    /// <summary>
    /// 读操作获取锁
    /// </summary>
    public Task<Releaser> ReaderLockAsync()
    {
        lock (m_waitingWriters)
        {
            if (m_status >= 0 && m_waitingWriters.Count == 0)
            {
                ++m_status;
                return m_readerReleaser;
            }
            else
            {
                // 存在等待的写操作或正在执行写操作
                ++m_readersWaiting;
                return m_waitingReader.Task.ContinueWith(t => t.Result);
            }
        }
    }
 
    /// <summary>
    /// 释放读操作
    /// </summary>
    private void ReaderRelease()
    {
        TaskCompletionSource<Releaser> toWake = null;
 
        lock (m_waitingWriters)
        {
            --m_status;
            if (m_status == 0 && m_waitingWriters.Count > 0)
            {
                // 唤醒的是最后一个激活的读操作并且存在写操作正在等待
                m_status = -1;
                toWake = m_waitingWriters.Dequeue();
            }
        }
 
        if (toWake != null)
            toWake.SetResult(new Releaser(this, true));
    }
 
    /// <summary>
    /// 写操作获取锁
    /// </summary>
    public Task<Releaser> WriterLockAsync()
    {
        lock (m_waitingWriters)
        {
            if (m_status == 0)
            {
                m_status = -1;
                return m_writerReleaser;
            }
            else
            {
                // 新的写操作被迫等待
                var waiter = new TaskCompletionSource<Releaser>();
                m_waitingWriters.Enqueue(waiter);
                return waiter.Task;
            }
        }
    }
 
    /// <summary>
    /// 释放写操作
    /// </summary>
    private void WriterRelease()
    {
        TaskCompletionSource<Releaser> toWake = null;
        bool toWakeIsWriter = false;
 
        lock (m_waitingWriters)
        {
            if (m_waitingWriters.Count > 0)
            {
                toWake = m_waitingWriters.Dequeue();
                toWakeIsWriter = true;
            }
            else if (m_readersWaiting > 0)
            {
                toWake = m_waitingReader;
                m_status = m_readersWaiting;
                m_readersWaiting = 0;
                m_waitingReader = new TaskCompletionSource<Releaser>();
            }
            else m_status = 0;
        }
 
        if (toWake != null)
            toWake.SetResult(new Releaser(this, toWakeIsWriter));
    }
 
    public struct Releaser : IDisposable
    {
        private readonly AsyncReaderWriterLock m_toRelease;
        // 变量标识是否是写操作,因为我们需要使用不同的方式来释放两种操作的锁
        private readonly bool m_writer;
 
        internal Releaser(AsyncReaderWriterLock toRelease, bool writer)
        {
            m_toRelease = toRelease;
            m_writer = writer;
        }
 
        public void Dispose()
        {
            if (m_toRelease != null)
            {
                if (m_writer)
                    m_toRelease.WriterRelease();
                else
                    m_toRelease.ReaderRelease();
            }
        }
    }
}
 
/// <summary>
/// 示例:AsyncReaderWriterLock
/// </summary>
public class AsyncReaderWriterLockTest
{
    public static async void Test()
    {
        AsyncReaderWriterLock m_lock = new AsyncReaderWriterLock();
        using (var releaser = await m_lock.WriterLockAsync())
        {
 
        }
 
        using (var releaser = await m_lock.ReaderLockAsync())
        {
 
        }
    }
}

推荐阅读:

异步编程:同步基元对象(上)

异步编程:同步基元对象(下)

构建Async版本的同步基元系列已经全部翻译完,我希望你喜欢它。感谢大家的观看……赞的话请多帮推荐(*^_^*)

原文:《Building Async Coordination Primitives, Part 7: AsyncReaderWriterLock》

作者:Stephen Toub – MSFT

【转】3.7(译)构建Async同步基元,Part 7 AsyncReaderWriterLock相关推荐

  1. 【转】3.6(译)构建Async同步基元,Part 6 AsyncLock

    传送门:异步编程系列目录-- 最近在学习.NET4.5关于"并行任务"的使用."并行任务"有自己的同步机制,没有显示给出类似如旧版本的:事件等待句柄.信号量.l ...

  2. 【转】3.5(译)构建Async同步基元,Part 5 AsyncSemaphore

    传送门:异步编程系列目录-- 最近在学习.NET4.5关于"并行任务"的使用."并行任务"有自己的同步机制,没有显示给出类似如旧版本的:事件等待句柄.信号量.l ...

  3. 【转】3.4(译)构建Async同步基元,Part 4 AsyncBarrier

    传送门:异步编程系列目录-- 最近在学习.NET4.5关于"并行任务"的使用."并行任务"有自己的同步机制,没有显示给出类似如旧版本的:事件等待句柄.信号量.l ...

  4. 【转】3.3(译)构建Async同步基元,Part 3 AsyncCountdownEvent

    传送门:异步编程系列目录-- 最近在学习.NET4.5关于"并行任务"的使用."并行任务"有自己的同步机制,没有显示给出类似如旧版本的:事件等待句柄.信号量.l ...

  5. 【转】3.2(译)构建Async同步基元,Part 2 AsyncAutoResetEvent

    传送门:异步编程系列目录-- 最近在学习.NET4.5关于"并行任务"的使用."并行任务"有自己的同步机制,没有显示给出类似如旧版本的:事件等待句柄.信号量.l ...

  6. 【转】3.1(译)构建Async同步基元,Part 1 AsyncManualResetEvent

    传送门:异步编程系列目录-- 最近在学习.NET4.5关于"并行任务"的使用."并行任务"有自己的同步机制,没有显示给出类似如旧版本的:事件等待句柄.信号量.l ...

  7. 【转】1.4异步编程:轻量级线程同步基元对象

    开始<异步编程:同步基元对象(下)> 示例:异步编程:轻量级线程同步基元对象.rar 在<异步编程:线程同步基元对象>中我介绍了.NET4.0之前为我们提供的各种同步基元(包括 ...

  8. 【转】1.3异步编程:线程同步基元对象

    开始<异步编程:同步基元对象(上)> 示例:异步编程:线程同步基元对象.rar 如今的应用程序越来越复杂,我们常常需要多线程技术来提高我们应用程序的响应速度.每个线程都由自己的线程ID,当 ...

  9. 几何基元_.NET异步协调基元中的两种技术比较

    几何基元 Last week in my post on updating my Windows Phone 7 application to Windows 8 I shared some code ...

最新文章

  1. 可综合的SystemVerilog:参数化函数/任务
  2. Java链表的基本使用
  3. MySQL 1093 You can't specify target table for update in FROM c 的解决办法
  4. 前端学习(1909)vue之电商管理系统电商系统之渲染修改用户的请求
  5. C语言extern与static修饰变量
  6. git回退历史版本无法上传_Git系列教程(二):版本库中添加文件、版本回退
  7. PHP将mysql数据导出为Excel
  8. Linux应用编程基础01:Linux应用编程绪论
  9. 末日帝国——Agile公司的困境 (5)
  10. 用双网卡实现跨网段访问(转载)
  11. 深度强化学习之演员—评论家(Actor—Critic)
  12. 使用scala使用fastjson将map转json报错
  13. Charm Bracelet(题目地址链接:https://acs.jxnu.edu.cn/problem/NOIOPJCH02067113)
  14. Java微信授权小程序获取用户昵称头像等基本信息
  15. Java笔记009-数组、排序、查找、多维数组(二维数组)
  16. 五十个html js特效动画,基于mo.js制作的17种炫酷图标动画特效
  17. 10.MATLAB方差分析
  18. Zigbee OSAL睡眠管理
  19. Gradle学习之Android-DSL AppExtension篇
  20. 车牌识别之一:车牌定位

热门文章

  1. redis 思维导图
  2. python文件读写小结
  3. vs设置html的模板快
  4. 2018-2019-2 20165212《网络攻防技术》Exp5 MSF基础应用
  5. 1114D . Flood Fill
  6. 使用offsetof对结构体指针偏移操作
  7. 免费开通二级域名的论坛
  8. 【数据结构与算法】【算法思想】贪心算法
  9. [密码学基础][每个信息安全博士生应该知道的52件事][Bristol Cryptography][第12篇]椭圆曲线上的群理论是什么
  10. [Leedcode][JAVA][第67题][二进制求和][位运算][字符串]