我应该使用const来使对象线程安全吗?

时间:2011-09-13 13:05:04

标签: c++ locking const

我写了一个类,其中几个线程可以访问实例。我用一个技巧来记住用户在使用之前必须锁定对象。它涉及只保留const实例。当需要读取或修改敏感数据时,其他类应该调用一个方法(这是const,因此允许)来获取锁定对象的非const版本。实际上它返回一个包含指向非const对象和scoped_lock的指针的代理对象,因此它在超出范围时解锁对象。代理对象也会重载operator->所以对对象的访问是透明的。

这样,通过访问未锁定的对象来拍摄自己的脚就更难了(总是有const_cast)。

应该避免“巧妙的伎俩”,无论如何这都闻起来很糟糕。

这个设计真的很糟糕吗? 我还能做什么?

编辑:Getters是非const来强制执行锁定。

4 个答案:

答案 0 :(得分:3)

基本问题:其他地方可能存在非const引用。如果安全地,那么它就不能安全地读取 - 你可能会看到一个中间状态。

此外,一些const方法可能(合法地)以线程不安全的方式修改隐藏的内部细节。

分析您对该对象的实际操作以及find an appropriate synchronisation mode

如果你的聪明容器确实对对象有足够的了解,可以通过代理控制所有同步,那么就将这些对象设为私有内部类。

答案 1 :(得分:2)

这很聪明,但不幸的是注定要失败。

spraff强调的问题是,您可以防止读取而不是写入。

考虑以下顺序:

unsigned getAverageSalary(Employee const& e) {
  return e.paid() / e.hired_duration();
}

如果我们在两个函数调用之间增加paid会发生什么?我们得到了一个不连贯的价值。

问题是您的方案没有明确强制执行锁定读取。

考虑代理模式的替代方案:对象本身是一组数据,所有私有。只有Proxy类(朋友)可以读取/写入其数据,并且在初始化Proxy时,它会自动抓取锁定(在对象的互斥锁上)。

class Data {
  friend class Proxy;
  Mutex _mutex;
  int _bar;
};

class Proxy {
public:
  Proxy(Data& data): _lock(data._mutex), _data(data) {}

  int bar() const { return _data._bar; }
  void bar(int b) { _data._bar = b; }

private:
  Proxy(Proxy const&) = delete; // disable copy

  Lock _lock;
  Data& _data;
};

答案 2 :(得分:0)

如果我想做你正在做的事情,我会做以下其中一项。

方法1:

shared_mutex m;  // somewhere outside the class

class A
{
private:
    int variable;
public:
    void lock() { m.lock(); }
    void unlock() { m.unlock(); }
    bool is_locked() { return m.is_locked(); }
    bool write_to_var(int newvalue)
    {
        if (!is_locked())
            return false;
        variable = newvalue;
        return true;
    }
    bool read_from_var(int *value)
    {
        if (!is_locked() || value == NULL)
            return false;
        *value = variable;
        return true;
    }
};

方法2:

shared_mutex m;  // somewhere outside the class

class A
{
private:
    int variable;
public:
    void write_to_var(int newvalue)
    {
        m.lock();
        variable = newvalue;
        m.unlock();
    }
    int read_from_var()
    {
        m.lock();
        int to_return = variable;
        m.unlock();
        return to_return;
    }
};

第一种方法更有效(不是一直锁定 - 解锁),但是,程序可能需要不断检查每次读写的输出以查看它们是否成功。第二种方法自动处理锁定,因此程序员甚至不知道锁是否存在。

注意:这不是复制粘贴的代码。它展示了一个概念并概述了它是如何完成的。请不要发表评论说你忘了在某处检查错误。

答案 3 :(得分:0)

这听起来很像Alexandrescu对volatile的看法。你不是 使用const的实际语义,而是利用它的方式 类型系统使用它。在这方面,我更喜欢Alexandrescu的使用 volatileconst具有非常明确且易于理解的语义, 并且颠覆它们肯定会给任何读书的人带来困惑 或维护代码。 volatile更合适,因为它没有 定义良好的语义,并且在大多数应用程序的上下文中,不是 用于其他任何事情。

而不是返回一个经典的代理对象,你应该返回一个 智能指针。实际上你可以使用shared_ptr来获取它 在返回值之前锁定,并在删除器中释放它 (而不是删除对象);然而,我宁愿害怕这一点 会引起读者之间的混淆,我可能会去 使用自定义智能指针(可能使用shared_ptr自定义 执行中的删除)。 (根据你的描述,我怀疑 无论如何,这更接近你的想法。)