Re: [问题] 关于std::mutex的应用

楼主: sarafciel (Cattuz)   2020-04-30 01:55:13
※ 引述《icetofux ()》之铭言:
: 标题: [问题] 关于std::mutex的应用
: 时间: Mon Apr 27 22:26:40 2020
:
: 开发平台(Platform): (Ex: Win10, Linux, ...)
: Win10/Linux
: 编译器(Ex: GCC, clang, VC++...)+目标环境(跟开发平台不同的话需列出)
: GCC
: 额外使用到的函数库(Library Used): (Ex: OpenGL, ...)
: None
: 问题(Question):
:
: 最近在使用C++11的std::thread,我已经知道若要在不同的thread存取同一个变量,
: 必须使用mutex来做管理才能达到Thread-Safe。
:
: 目前遇到的问题是,若我有多个不同的变量分别必须在多个不同的thread内存取,我
: 除了变量名称外,还必须一一产生对应的mutex,我想建立下列这样的样板类别:
:
: template <class T>
: class SharedVariable {
: private:
: std::mutex mtx;
: T data;
: public:
: T Get(void) {
: T data_cpy;
: std::lock_guard<std::mutex> lck(mtx);
: data_cpy = this->data;
: return data_cpy;
: }
: void Set(const T data) {
: std::lock_guard<std::mutex> lck(mtx);
: this->data = data;
: }
: };
:
: 在产生变量物件的同时,该物件也同时具有一个不用额外命名的mutex,并且当我
: 透过Get/Set存取变量时,也自动做好了上锁、解锁的功能。
:
: SharedVariable<int> shared_int;
: SharedVariable<std::string> shared_string;
: SharedVariable<std::vector<double>> shared_vector;
:
: shared_int.Set(123);
: int a = shared_int.Get();
:
: 目前比较让我有疑虑的是,在不同的thread内使用物件本身(如上例的shared_int、
: shared_string、shared_vector)是一个Thread-Safe的行为吗?我不确定要如何判
: 断,想请有经验的先进指教。
想回的东西比较多,就单独回一篇。
有错还请务必指正。
thread-safe比较简单的一种判断方式是,当某个thread在中途被context switch掉
其他的thread看到的状态到底是不是对的,如果是不对的,
你不能给他看,白话文讲就是要锁起来
不考虑OOE跟memory barrier,某个物件跟两条thread的关系可以粗略分三种:
1.两条都只有读
2.一条读另一条读/写
3.两条都要做读/写
第1种case就例如我先建一个质数表给两个thread查,
因为这个质数表的状态是不会变的,这个时候是不用锁的
第2种case的例子就有点像你开DMA buffer给某个硬件装置写,
你再去读这个buffer做处理
这时候写的那边不用顾虑读错的问题,因为只有他写
他看到的状态一定是最新,只读的那边则要去确保他看到的是最新的状态,
视处理的任务而定,也有可能要做到每次状态变更都要能知道
所以通常是读的那端会做polling,或是写的打interrupt通知之类的手段
第3种才是最常见的,两边都要做读写,此时就会有race condition的问题
但这不表示说,你把读写都各加一道大锁就会没事
因为读写有时候会有相依性,这个相依性会导致你要像原推文steve大讲的那样
你要保证他的顺序性,或是在写之前,你要确保他读到的data是最新状态才会对
后面这个就是compare and swap在做的事情
最简单的例子就是i++,因为你写回去i的值跟i本来的值有关
也就是我那段code写的那样,你的Get跟Set中间因为有一个解锁的空隙
在这个空隙里,如果thread A有context switch,B 就有可能拿到lock
导致读写序变成: A读->B读->A写->B写
或是A读->B读->B写->A写
而不论是哪个顺序,最后都会变成A或B的其中一条thread对i的影响被盖掉
这是为什么i++这种操作会弄成atomic的fetch_add的原因
因为你就是要把他的顺序涵盖进critical section里他才会对
另外就是你这边Get出去都是copy,这样子虽然可以保证thread有各自的instance
但在Set的时候就会变成最终只有几条thread的modify是有效的
而你就算是改用reference,因为物件的operation没有锁
这里不加锁就没办法保证正确性了
所以一般是不会像你这样用在存取上加锁的方式来处理多执行绪问题,
因为真的要做,你可能至少要把物件提供的operation都包一层锁,
而这样还不够,因为还是会有上面提到的读写相依性的问题要解决
那如果要做到这个地步,还不如看情况在function内做针对性的加锁来的省事XD
而与其去直面多thread同时读写多个物件这个难题
不如像love大跟kobe大讲的那样
把物件的写权交给单个thread,然后其他thread透过concurrent queue
去下command给这个thread写,
这样子相当于是把第3种case给抽象成第2种case来做,会好处理很多
:
: 谢谢。
:
: 喂入的资料(Input):
: None
: 预期的正确结果(Expected Output):
: None
: 错误结果(Wrong Output):
: None
: 程式码(Code):(请善用置底文网页, 记得排版,禁止使用图档)
: None
: 补充说明(Supplement):
:
:
:
作者: icetofux   2020-04-30 07:16:00
谢谢你的范例跟说明,因为我的情况属于第二种案例,所以我的做法确实没考虑到第三种案例的可能会发生的缺陷。

Links booklink

Contact Us: admin [ a t ] ucptt.com