作者sarafciel (Cattuz)
看板C_and_CPP
标题Re: [问题] 关於std::mutex的应用
时间Thu Apr 30 01:55:13 2020
※ 引述《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):
:
:
: --
:
※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 118.169.91.49 (台湾)
: ※ 文章网址: https://webptt.com/cn.aspx?n=bbs/C_and_CPP/M.1587997603.A.BCE.html
: → loveme00835: 或者你应该思考的是: 为什麽多个执行绪都可以去更改 04/27 23:43
: → loveme00835: 同个物件的状态, 而不是让单个专责的执行绪来做呢? 04/27 23:43
: → loveme00835: 可以参考看看 active object pattern 把两者先松绑 04/27 23:43
: → icetofux: 我的程式由9个thread构成,8个各自跑不同的流程,透过so 04/28 07:45
: → icetofux: cket与不同设备通讯,1个负责GUI,把8个设备的细部资料 04/28 07:45
: → icetofux: 显示出来,每个设备约有60多种的细部资料。文中所提在th 04/28 07:45
: → icetofux: read间共用的变数就是设备传输至GUI的细部资料。 04/28 07:45
: → icetofux: 谢谢loveme00835,我刚刚google了active object pattern 04/28 09:01
: → icetofux: ,好像就是用来避免大量mutex的设计模式,虽然没把握马 04/28 09:01
: → icetofux: 上带入应用,但是一个可以努力的方向。 04/28 09:01
: 推 eye5002003: 这情况不是用std::atomic比较适合吗? 04/28 09:47
: 以我对atomic的认识,似乎只有几种基本型态有提供atomic,我不是很确定像是string或是vector能不能使用。
: → Lipraxde: 一般不是保护 critical section 吗?每个变数都给一个 04/28 11:56
: → Lipraxde: mute lock 好像比较少看到 04/28 11:56
: → Lipraxde: mute -> mutex 04/28 11:56
: → kobe8112: 如果改成各设备更新的资料丢到各自的queue中,UI更新的 04/28 11:59
: → kobe8112: Thread轮询各queue是否有资料需更新呢? 04/28 12:00
: ※ 编辑: icetofux (111.71.40.212 台湾), 04/28/2020 12:52:59
: → sarafciel: https://ideone.com/wHfCXi 拿你的模板写了一小段 04/28 13:53
: → sarafciel: 应该很容易看出问题才是 04/28 13:54
: 推 steak5566: 可以请楼上大大可以解说一下为什麽吗 04/28 22:55
: → Caesar08: sarafciel的strt_flag型态要改成atomic<bool> 04/29 13:37
对 这边要写atomic<bool>,我疏忽了QQ
: → Caesar08: 另外,func与func2不相等,func保护的是整段过程 04/29 13:38
: → Caesar08: func2只保护每次sv_int的read write 04/29 13:38
: → Caesar08: 结果不一样是正常的 04/29 13:38
: → Caesar08: 你的Get直接return data_cpy就好,不用先create再copy 04/29 13:40
: → Caesar08: 另外有支援C++ 17的话,用shared_mutex会比mutex好 04/29 13:41
: 推 steve1012: 不是有锁住就好 还要看你想保护的东西是什麽 比如说你 04/29 14:04
: → steve1012: 各个function 之间有没有什麽顺序先後需要保证的 04/29 14:04
: → steve1012: muli threading 写的越简单通常越好 容易看出有没有问 04/29 14:04
: → steve1012: 题 04/29 14:04
--
※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 123.193.54.11 (台湾)
※ 文章网址: https://webptt.com/cn.aspx?n=bbs/C_and_CPP/M.1588182916.A.13A.html
1F:推 icetofux: 谢谢你的范例跟说明,因为我的情况属於第二种案例,所以 04/30 07:16
2F:→ icetofux: 我的做法确实没考虑到第三种案例的可能会发生的缺陷。 04/30 07:16