【问题标题】:C++ multithreaded access of boolean member布尔成员的 C++ 多线程访问
【发布时间】:2021-10-19 23:07:52
【问题描述】:

由于低延迟要求,我正在使用 for_each 的并行执行来识别句子(一组字符串)的意图。我之前了解到,不需要互斥锁来保护 bool 或任何其大小小于一个字节的数据类型。所以我问访问布尔成员 Tag.m_Found 是否是线程安全的?否则,我应该使用 atomic 还是 mutex?

#include <iostream>
#include <unordered_set>
#include <set>
#include <list>
#include <algorithm>
#include <execution>
struct Tag{
    const std::unordered_set<std::string> m_Context;
    const std::string m_Name;
    volatile bool m_Found;
    Tag(const std::unordered_set<std::string> context, const std::string name)
        : m_Context(context)
        , m_Name(name)
        , m_Found(false)
        {}
    Tag(const Tag & tag) = delete;
    Tag(Tag && tag) = default;
    Tag & operator=(const Tag & tag) = delete;
};
int main(){
    const std::set<std::string> input = {"hello", "my", "son"};
    std::list<Tag> intentions;
    intentions.emplace_back(Tag({"hello", "Hi", "morning"}, "greeting"));
    intentions.emplace_back(Tag({"father", "mother", "son"}, "family"));
    intentions.emplace_back(Tag({"car", "bus", "airplan"}, "transportation"));
    for_each( std::execution::par
            , std::begin(input)
            , std::end(input)
            , [& intentions](const std::string & input_element)
                {
                    for_each( std::execution::par
                            , std::begin(intentions)
                            , std::end(intentions)
                            , [& input_element](Tag & intention){
                                if(!intention.m_Found){
                                    intention.m_Found = intention.m_Context.find(input_element)!=intention.m_Context.end();
                                }
                            }
                        );
                }
        );
    for_each( std::execution::seq
            , std::begin(intentions)
            , std::end(intentions)
            , [](Tag & intention){
                if(intention.m_Found){
                    std::cout<<intention.m_Name;
                }
            }
        );
    return 0;
}

【问题讨论】:

  • “我之前了解到,不需要互斥锁来保护 bool 或任何其大小小于 1 字节的数据类型。”对不起,但这是完全错误的。对任何对象的并发读/写访问需要互斥锁(或其他同步)或std::atomic;否则你有一个未定义行为的数据竞争。 bool也不例外,也不根据对象的大小,volatile也无济于事。
  • 不相关:volatile 对多线程没有帮助。为什么你还有它?
  • 同样,没有小于一字节的数据类型。
  • @IgorTandetnik:除了位域,对他们来说原子性情况更糟,因为每次写入都必须 RMW。
  • @NateEldredge 位字段不是类型。

标签: c++ foreach c++17 mutex atomic


【解决方案1】:

问题是如果添加“std::mutex m_Found_access;”或制作“atomic_bool m_Found;”默认的移动构造函数被删除,所以我需要为标签定义一个移动构造函数。并且 m_Found 应该只设置为 true 以避免竞争条件(如@Nate Eldredge 所述)。代码变为:

#include <iostream>
#include <unordered_set>
#include <set>
#include <list>
#include <algorithm>
#include <execution>
struct Tag{
    const std::unordered_set<std::string> m_Context;
    const std::string m_Name;
    std::atomic_bool m_Found;
    Tag(const std::unordered_set<std::string> context, const std::string name)
        : m_Context(context)
        , m_Name(name)
        , m_Found(false)
        {}
    Tag(const Tag & tag) = delete;
    Tag & operator=(const Tag & tag) = delete;
    Tag(Tag && tag) : m_Context(std::move(tag.m_Context))
                    , m_Name(std::move(tag.m_Name))
                    , m_Found(static_cast< bool >(tag.m_Found))
                    {}
};
int main(){
    const std::set<std::string> input = {"hello", "my", "son"};
    std::list<Tag> intentions;
    intentions.emplace_back(Tag({"hello", "Hi", "morning"}, "greeting"));
    intentions.emplace_back(Tag({"father", "mother", "son"}, "family"));
    intentions.emplace_back(Tag({"car", "bus", "airplan"}, "transportation"));
    for_each( std::execution::par
            , std::begin(input)
            , std::end(input)
            , [& intentions](const std::string & input_element)
                {
                    for_each( std::execution::par
                            , std::begin(intentions)
                            , std::end(intentions)
                            , [& input_element](Tag & intention){
                                if(!intention.m_Found){
                                    if(intention.m_Context.find(input_element)!=intention.m_Context.end()){
                                        intention.m_Found = true;
                                    }
                                }
                            }
                        );
                }
        );
    for_each( std::execution::seq
            , std::begin(intentions)
            , std::end(intentions)
            , [](Tag & intention){
                if(intention.m_Found){
                    std::cout<<intention.m_Name;
                }
            }
        );
    return 0;
}

【讨论】:

  • 请注意,您还可以通过访问std::memory_order_relaxed 进行一些优化,因为该标志不控制对任何共享资源的访问,也不需要对任何内容进行排序。您也可以使用 std::atomic_flag 代替 std::atomic&lt;bool&gt; 以保证它是无锁的,尽管在大多数实现中它们是相同的。
  • 感谢@Nate Eldredge,我已经更新了代码以避免竞争条件。
猜你喜欢
  • 2021-08-07
  • 2018-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-13
相关资源
最近更新 更多