C ++线程安全 - 地图阅读

时间:2016-03-12 15:53:26

标签: c++ multithreading thread-safety

我正在开发一个需要(defun list-notation-to-dot-notation (lst) (cond ((atom lst) lst) ((null (cdr lst)) (list (list-notation-to-dot-notation (car lst)) '\. 'NIL)) (t (list (list-notation-to-dot-notation (car lst)) '\. (list-notation-to-dot-notation (cdr lst)))))) (defun list-2-dot (lst) (format t "~a" (list-notation-to-dot-notation lst))) (list-2-dot '(a)) => (A . NIL) (list-2-dot '(a b)) => (A . (B . NIL)) (list-2-dot '((a) b)) => ((A . NIL) . (B . NIL)) (list-2-dot '(a (b) c)) => (A . ((B . NIL) . (C . NIL))) (list-2-dot '(a b (c))) => (A . (B . ((C . NIL) . NIL))) (list-2-dot '((a) (b) (c))) => ((A . NIL) . ((B . NIL) . ((C . NIL) . NIL))) 的程序,特别是像std::map这样的程序 - 它的意思是银行更改率 - 第一个字符串是原始货币,而第一个字符串是第二个映射是所需的映射,int是它们的速率。整个地图将只读。我还需要互斥锁吗?我对整个线程的安全性感到有点困惑,因为这是我的第一个更大的多线程程序。

4 个答案:

答案 0 :(得分:13)

如果您正在讨论标准std::map 并且没有线程写入,则不需要同步。没有写入的并发读取很好。

但是,如果至少有一个线程在地图上执行写操作,则确实需要某种保护,如互斥锁。

请注意std::map::operator[]计为写入,因此请使用std::map::at(如果地图中可能不存在密钥,则使用std::map::find)。只需通过const map&引用共享地图,您就可以使编译器保护您免受意外写入。

澄清了OP的情况。为了完整起见:请注意,其他类可能有mutable个成员。对于那些人来说,即使通过const&访问也可能会引发竞争。如有疑问,请查看文档或使用其他内容进行并行编程。

答案 1 :(得分:3)

经验法则是,如果您有共享数据,并且至少有一个线程将是编写器,那么您需要同步。如果其中一个线程是编写器,则必须具有同步,因为您不希望读取器读取正在写入的元素。这可能会导致问题,因为读者可能会读取部分旧值和部分新值。

在你的情况下,因为所有的线程都只会读取数据,所以他们无法做任何会影响地图的事情,所以你可以进行并发(非同步)读取。

答案 2 :(得分:2)

std::map<std::string, std::map<std::string,int>> const换成只有const成员函数 [*] 的自定义类。

这将确保在创建之后使用该类对象的所有线程只能从中读取,这保证了自C ++ 11以来的安全性。

正如documentation所说:

  

所有const成员函数可以由不同的同时调用   同一个容器上的线程。

无论如何,将容器包装在您自己的自定义类型中是一种很好的做法。增加线程安全性只是该良好实践的一个积极的副作用。其他积极影响包括提高客户端代码的可读性,减少/调整容器接口到所需功能,更容易添加额外的约束和检查。

这是一个简短的例子:

class BankChangeRates
{
public:

    BankChangeRates(std::map<std::string, std::map<std::string,int>> const& data) : data(data) {}

    int get(std::string const& key, std::string const& inner_key) const
    {
        auto const find_iter = data.find(key);
        if (find_iter != data.end())
        {
            auto const inner_find_iter = find_iter->second.find(inner_key);
            if (inner_find_iter != find_iter->second.end())
            {
                return inner_find_iter->second;
            }
        }
        // error handling
    }

    int size() const
    {
        return data.size();
    }

private:
    std::map<std::string, std::map<std::string,int>> const data;
};

在任何情况下,线程安全问题都会减少到如何确保构造函数不从另一个线程写入的对象读取。这通常是平凡的;例如,可以在多线程开始之前构造对象,或者可以用硬编码的初始化列表初始化对象。在许多其他情况下,创建对象的代码通常只访问其他线程安全函数和本地对象。

重点是,对象的并发访问在创建后始终是安全的。

[*] 当然,const成员函数应该遵守承诺,而不是尝试使用mutableconst_cast进行“变通办法”。功能

答案 3 :(得分:1)

如果您完全确定这两张地图总是一目了然,那么您永远不需要互斥锁。

但是你必须格外小心,没有人可以在程序执行期间以任何方式更新地图。确保您在程序的初始阶段初始化地图,然后再也不会因任何原因更新它。

如果您对此感到困惑,将来您可能需要在程序执行之间更新它,那么最好在地图周围放置宏,这些宏现在是空的。在将来,如果你需要互斥锁,只需更改宏定义。

PS ::我在回答中使用了地图,可以很容易地被共享资源取代。这是为了便于理解