多线程程序设计(六)——Read-Write Lock
本文摘要了《Java多线程设计模式》一书中提及的 Read-Write Lock 模式的适用场景,并针对书中例子(若干名称有微调)给出一份 C++ 参考实现及其 UML 逻辑图,也列出与之相关的模式。
◆ 适用场景
当读取共享数据的线程多于更新共享数据的线程时,为了提高整体的吞吐量,并保证数据的安全性。
◆ 解决方案
在共享数据的物理锁(std::mutex)基础上,为“读”、“写”这两种操作建立不同的“逻辑锁”。使用 std::condition_variable 对象控制允许“读”、“写”的条件。在优先“读”操作时,允许多个“读”线程访问共享数据,但让“写”线程等待。当“读”操作完成后,唤醒等待的所有线程,并让“写”操作优先执行。在优先“写”操作时,仅允许一个“写”线程访问共享数据,让其它的“读”、“写”线程等待。当“写”操作完成后,唤醒等待的所有线程,并让“读”操作优先执行。
◆ 参考实现
例子模拟了 6 条“读”线程(Reader)和 2 条“写”线程(Writer)并行访问共享数据(Data)的过程。当 Reader 从 Data 中读取内容时,不允许所有的 Writer 向 Data 中写入新的内容,但允许其它的 Reader 读取内容;当 Writer 向 Data 中写入新的内容时,不允许其它的 Reader 和 Writer 读写 Data 中的内容。
class Reader
{
    ...
    Data &
    __data__;
    ...
    void
    run()
    {
        while (true) {                              #1
            string content = __data__.read();
            std::printf("%s reads %s\n", __name__.c_str(), content.c_str());
            std::this_thread::sleep_for(milliseconds(std::rand() % 700));
        }
    }
};
class Writer
{
    ...
    Data &
    __data__;
    ...    
    void
    run()
    {
        while (true) {                          #2
            char c = __nextchar__();
            __data__.write(c);
            std::this_thread::sleep_for(milliseconds(std::rand() % 300));
        }
    }
};
Reader 每隔 0 ~ 700 毫秒从 Data 中读取内容(#1),而 Writer 每隔 0 ~ 300 毫秒将某个字符作为新的内容写入到 Data 中(#2)。
class Data
{
    ...
    
    string
    __buffer__;                     #1
    Read_Write_Lock
    __lock__;
    string
    __do_read__()                   #2
    {
        return __buffer__;
    }
    void
    __do_write__(char c)                #2
    {
        for (int i = 0; i < __capacity__; ++i) {
            __buffer__[i] = c;
            std::this_thread::sleep_for(milliseconds(50));
        }
    }
    ...    
    string
    read()
    {
        __lock__.read_lock();                   #3
        string content = __do_read__();
        __lock__.read_unlock();                 #3
        return content;
    }
    void
    write(char c)
    {
        __lock__.write_lock();                  #4
        __do_write__(c);
        __lock__.write_unlock();                #4
    }
};
Data 封装了对共享数据(#1)的实际读写(#2)操作。在读数据的前后,要分别获取“(逻辑)读”锁、释放“读”锁操作(#3);在写数据的前后,要分别获取“(逻辑)写”锁、释放“写”锁操作(#4)。
对共享数据的读写会产生 读-写(R-W)、写-写(W-W) 两种冲突,如下表所示:
| 读 | 写 | |
|---|---|---|
| 读 | - | R-W | 
| 写 | R-W | W-W | 
这两种冲突由 Read_Write_Lock 来控制,
class Read_Write_Lock
{
    ...
    int
    __readingreaders__;
    ...
    __waitingwriters__;
    ...
    __writingwriters__;
    bool
    __preferwriter__;               #1
    mutex
    __mtx__;
    condition_variable
    __cv__;
    ...
    void
    read_lock()
    {
        unique_lock<mutex> lk(__mtx__);
        __cv__.wait(lk,                                 #2
                    [this] {
                        bool write = __writingwriters__ > 0 ||
                                    (__preferwriter__ && __waitingwriters__ > 0);
                        if (write)
                            std::printf("Somebody is writing or to write. Waiting to read...\n");
                        return !write;
                    });
        ++__readingreaders__;
    }
    void
    read_unlock()
    {
        lock_guard<mutex> lk(__mtx__);
        --__readingreaders__;                           #3
        __preferwriter__ = true;
        __cv__.notify_all();
    }
    void
    write_lock()
    {
        unique_lock<mutex> lk(__mtx__);
        ++__waitingwriters__;
        __cv__.wait(lk,                                 #4
                    [this] {
                        bool nowrite = __readingreaders__ > 0 || __writingwriters__ > 0;
                        if (nowrite)
                            std::printf("Somebody is reading or writing. Waiting to write...\n");
                        return !nowrite;
                    });
        --__waitingwriters__;
        ++__writingwriters__;
    }
    void
    write_unlock()
    {
        lock_guard<mutex> lk(__mtx__);
        --__writingwriters__;                           #5
        __preferwriter__ = false;
        __cv__.notify_all();
    }
};
__readingreaders__ 代表正在读数据的线程数量,__waitingwriters__ 代表等待写数据的线程数量,__writingwriters__ 代表正在写数据的线程数量,__preferwriter__ 标识优先读或写操作(#1)。读-写(R-W)、写-写(W-W)这两种冲突实际上由这 4 个变量值加以控制的。
- read_lock(): 获取“读”锁时,如果有正在写数据或者要优先写数据的线程,则需要等待写数据的操作完成(#2)。在增加了读数据的线程数量后,才能允许后续读数据的操作。
 - read_unlock(): 释放“读”锁时,在减少读数据的线程数量后,优先让位于写数据的线程并唤醒等待中的所有线程(#3)。
 - write_lock(): 获取“写”锁时,如果有正在读数据或者写数据的线程,则需要等待这些操作完成(#4)。在增加了写数据的线程数量后,才能允许后续写数据的操作。
 - write_unlock(): 释放“写”锁时,在减少了写数据的线程数量后,优先让位于读数据的线程并唤醒等待中的所有线程(#5)。
 
以下类图展现了代码主要逻辑结构,

以下顺序图展现了线程并发中的交互。

◆ 验证测试
笔者在实验环境一中编译代码(-std=c++11)成功后运行可执行文件,
$ g++ -std=c++11 -lpthread read-write_lock.cpp
$ ./a.out
运行结果如下:
...
Somebody is writing or to write. Waiting to read...
Somebody is reading or writing. Waiting to write...
Somebody is writing or to write. Waiting to read...
Writer-2 wrote G
Reader-3 read GGGGGGGGGGGGGGGGGGGGGGGGGG
Somebody is writing or to write. Waiting to read...
Somebody is writing or to write. Waiting to read...
Somebody is writing or to write. Waiting to read...
Somebody is writing or to write. Waiting to read...
Somebody is writing or to write. Waiting to read...
Somebody is writing or to write. Waiting to read...
Somebody is reading or writing. Waiting to write...
Writer-1 wrote h
Reader-6 read hhhhhhhhhhhhhhhhhhhhhhhhhh
Somebody is writing or to write. Waiting to read...
Somebody is writing or to write. Waiting to read...
Somebody is writing or to write. Waiting to read...
Somebody is writing or to write. Waiting to read...
Somebody is writing or to write. Waiting to read...
Somebody is reading or writing. Waiting to write...
Somebody is writing or to write. Waiting to read...
Writer-2 wrote H
Reader-1 read HHHHHHHHHHHHHHHHHHHHHHHHHH
Somebody is writing or to write. Waiting to read..
...
可以看到读写线程的交替运行。当写线程完成后,由读线程读取数据;而读线程完成后,又由写线程写入数据。
◆ 相关模式
- 此模式中 Read_Writer_Lock 进行共享互斥的地方用到了 Guarded Suspension 模式。
 - 完全没有 Writer 参与者的时候,可使用 Immutable 模式。
 
◆ 最后
完整的代码请参考 [gitee] cnblogs/18807637 。更多模式请参考多线程程序设计。
致《Java多线程设计模式》的作者结城浩。写作中也参考了《C++并发编程实战》中的若干建议,致作者 Anthony Williams 和译者周全等。
受限于作者的水平,读者如发现有任何错误或有疑问之处,请追加评论或发邮件联系 green-pi@qq.com。作者将在收到意见后的第一时间里予以回复。 本文来自博客园,作者:green-cnblogs,转载请注明原文链接:https://www.cnblogs.com/green-cnblogs/p/18807637 谢谢!
                    
                
                
            
        
浙公网安备 33010602011771号