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

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

最近在学习.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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/437144.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面向对象软件开发代码结构(1)

类内部结构 类内部架构实际上是一个小型的状态机&#xff0c;成员变量是状态变量&#xff0c;成员函数是处理机。一般提倡一个类实现一种特定的功能&#xff0c;这样可以降低实现的复杂性&#xff0c;状态机越简单&#xff0c;越利于实现。 实例间通信 软件的功能是多个模块…

python猜数字1001untitled_ML - Python 基础

数据类型 Numeric & String1. Python数据类型1.1 总体&#xff1a;numerics, sequences, mappings, classes, instances, and exceptions1.2 Numeric Types: int (包含boolean), float, complex1.3 int: unlimited length; float: 实现用double in C, 可查看 sys.float_inf…

【转】4.1触碰jQuery:AJAX异步详解

传送门&#xff1a;异步编程系列目录…… 示例源码&#xff1a;触碰jQuery&#xff1a;AJAX异步详解.rar AJAX 全称 Asynchronous JavaScript and XML&#xff08;异步的 JavaScript 和 XML&#xff09;。它并非一种新的技术&#xff0c;而是以下几种原有技术的结合体。 1) 使…

QStackedWidget实现自适应紧凑布局

前言 本文提出了一种使QStackedWidget尺寸根据内容自适应调整的解决方法。 问题提出 我们知道&#xff0c;QStackedWidget可以包含多个可切换的子窗口。多个子窗口的高度不一样时&#xff0c;此时将QStackedWidget放在一个垂直布局中&#xff0c;所有子窗口会保持和最高的子…

linux查看tcl版本_查看Linux内核版本的方法有几个?你也是这样操作吗?

请关注本头条号&#xff0c;每天坚持更新原创干货技术文章。如需学习视频&#xff0c;请在微信搜索公众号“智传网优”直接开始自助视频学习1. 前言内核是操作系统的核心组件。 它管理系统的资源&#xff0c;是计算机硬件和软件之间的桥梁。您可能因多种原因需要确切知道GNU / …

【转】4.2使用jQuery.form插件,实现完美的表单异步提交

传送门&#xff1a;异步编程系列目录…… 示例下载&#xff1a;使用jQuery.form插件&#xff0c;实现完美的表单异步提交.rar 抓住6月份的尾巴&#xff0c;今天的主题是 今天我想介绍的是一款jQuery的插件&#xff1a;Jquery.form.js 官网。 通过该插件&#xff0c;我们可以非常…

python医学数据挖掘_GitHub - SSSzhangSSS/Python-Data-mining-Tutorial: Python数据挖掘教程

Python数据挖掘教程作者 : 长行说明 : 本教程以9周的数据挖掘教程为主&#xff0c;每周包括5天的知识学习和2天的案例实现。以周为阶段&#xff0c;每周包括5天的知识内容(Day)、1天的案例实现(Example)和1天的小测验(Test)&#xff1b;此外还可能包含选学部分(Extra)。案例的难…

面向对象软件开发代码结构(2)

使用封装降低信息的复杂度 封装是面向对象编程的核心思想之一。 封装的过程&#xff0c;是将大量的信息&#xff08;过程、数据&#xff09;&#xff0c;凝缩成满足特定需求的接口的过程。 从数量上来说&#xff0c;好的封装必然是将大量的、与业务交互无关的实现细节隐藏起来…

什么方式可以通过影子系统传播恶意代码_将恶意代码隐藏在图像中:揭秘恶意软件使用的隐写术...

概述本周&#xff0c;许多Facebook用户都会发现&#xff0c;一些用户发布图片上出现了原本应该隐藏的图像标签。由此可以证明&#xff0c;图像可以携带大量表面上不可见的数据。实际上&#xff0c;Facebook和Instagram所使用的图片元数据与恶意攻击者制作的特制图像相比显得非常…

一种类的渐进式开发写法

// 主类&#xff0c;一般为窗口类 class MainClass { public:FuncClass1 *a;FuncClass2 *b; }// 实现某个功能的类 class FuncClass1 { public:FuncClass1(MainClass *) }// 实现某个功能的类 class FuncClass2 { public:FuncClass2(MainClass *) }每加一个大的功能&#xff0c…

【转】SQL中where, group by, having的用法和区别

group by,where,having 是数据库查询中最常用的几个关键字。在工作中&#xff0c;时常用到&#xff0c;那么&#xff0c;当一个查询中使用了where ,group by ,having及聚集函数时 &#xff0c;执行顺序是怎么样的&#xff1f;为了回答这个问题&#xff0c;将这个三个关键字的用…

无法嵌入互操作类型 请改用适用的接口_西门子COMOS软件开发定制学习7-嵌入谷歌浏览器内核...

首先需要声明的是&#xff0c;本篇并非COMOS实用案例&#xff0c;只是希望借此让大家了解&#xff0c;如何使用微软的WPF和C#语言开发COMOS插件。首先看下效果图功能说明&#xff1a;拖拽COMOS设备至定制的浏览器&#xff0c;自动根据设备的名称和其制造商参数值&#xff0c;搜…

Win10上VMware的问题汇总

装xp很卡顿的问题 卸载360&#xff0c;重启电脑即可。 拖拽文件/文件夹到虚拟机直接卡住 使用15.1版本的VMware即可。 资源&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1dtr_cPwzprRTznpxj-OKTw 提取码&#xff1a;1wpj

【转】C#与C++的发展历程第一 - 由C#3.0起

C#5.0作为第五个C#的重要版本&#xff0c;将异步编程的易用度推向一个新的高峰。通过新增的async和await关键字&#xff0c;几乎可以使用同编写同步代码一样的方式来编写异步代码。 本文将重点介绍下新版C#的异步特性以及部分其他方面的改进。同时也将介绍WinRT程序一些异步编…

python数据库实例_Python操作MySQL数据库9个实用实例

用python连接mysql的时候&#xff0c;需要用的安装版本&#xff0c;源码版本容易有错误提示。下边是打包了32与64版本。MySQL-python-1.2.3.win32-py2.7.exeMySQL-python-1.2.3.win-amd64-py2.7.exe实例 1、取得 MYSQL 的版本实例 2、创建一个表并且插入数据实例 3、 python 使…

Win10+VMware上安装macOS过程记录

2021年更新 主要参考文章&#xff1a;https://blog.csdn.net/qq_40143985/article/details/104011778 参考了其他一些文章&#xff0c;最后会出现…not successfully错误&#xff0c;安装失败。建议参考这篇文章。 FAQ 安装好macOS后&#xff0c;电脑运行有点卡的问题&#x…

【转】5.2高性能IO模型浅析

服务器端编程经常需要构造高性能的IO模型&#xff0c;常见的IO模型有四种&#xff1a; &#xff08;1&#xff09;同步阻塞IO&#xff08;Blocking IO&#xff09;&#xff1a;即传统的IO模型。 &#xff08;2&#xff09;同步非阻塞IO&#xff08;Non-blocking IO&#xff0…

vba 修改文本文档 指定行_VBA程序报错,用调试三法宝,bug不存在的

如果把VBA比作一门刀法&#xff0c;那么经过前面内容的操练&#xff0c;大家已经掌握了很多实用的招式。如果我们在刀法招式的基础之上&#xff0c;再掌握更多的“磨刀”心法&#xff0c;那么我们的刀用起来才会又好又快。所以今天主要和大家分享——VBA“磨刀”心法之程序调试…

VMware使用

重要功能 快照 快照就是保存当前虚拟机完整状态&#xff0c;相当于手动克隆一个虚拟机副本&#xff0c;也相当于是git中的一个提交点。在安装好一个新的虚拟机之后&#xff0c;一般都要创建一个快照&#xff0c;便于日后恢复。

【转】VS TFS源码分析软件PATFS使用方法一:配置团队项目

# 项目交付用正版&#xff0c;省下一台Iphone12 # # 31款JAVA开发必备控件和工具 # 相关链接&#xff1a; VS TFS源码分析软件PATFS使用方法二&#xff1a;设置新数据检查间隔VS TFS源码分析软件PATFS使用方法三&#xff1a;数据附件大小限制的自定义设置VS TFS源码分析软件P…