以线程安全的方式与外部世界共享数据成员

时间:2010-12-15 12:35:27

标签: c++ multithreading synchronization locking resource-management

我真的很感激有关此事的一些建议。

e.g。

class Foo
{
    TData data;
public:
    TData *getData() { return &data; } // How can we do this in a thread safe manner ?
};

所以我希望有一种机制使getData()线程安全。我已经提出了我自己的解决方案,其中涉及将数据成员打包到以下模板类中,其中包含用于同步对其的访问的互斥锁。你怎么看 ?可能存在哪些问题?

class locked_object : boost::noncopyable
{
    T *object;
    TLockable *lock;
    bool locked;

public:
    locked_object(T *_object, TLockable *_lock) : object(_object), lock(_lock), locked(true)
    {
        lock->lock();
    }

    ~locked_object()
    {
        lock->unlock();
    }

    T *get()
    {
        _ASSERT(locked);
        if (!locked)
            throw new std::exception("Synchronization error ! Object lock is already released !");
        return this->tobject;
    }


    void unlock()
    {
        locked = false;

        lock->unlock();
    }

    T *operator ->() const
    {   
        _ASSERT(locked);
        if (!locked)
            throw new std::exception("Synchronization error ! Object lock is already released !");

        return this->tobject; 
    }

    operator T *() const
    {
        _ASSERT(locked);
        if (!locked)
            throw new std::exception("Synchronization error ! Object lock is already released !");

        return this->tobject;
    }
};

提前感谢您的任何意见和建议。

法提赫

4 个答案:

答案 0 :(得分:2)

您的设计会对用户负责,以确保在正确的时间锁定和解锁对象。即使您的锁定对象进行错误检查,它也不会涵盖所有基础(例如,在完成对象时忘记释放对象)

因此,假设您有不安全的对象TData。您将其包含在Foo中,而不是Foo返回指向TData的指针,重新实现TDataFoo中的所有公共方法,但使用锁定和解锁。

这与pImpl模式非常相似,除了您的接口在调用实现之前添加锁。这样,用户只知道对象是线程安全的,不需要担心同步。

答案 1 :(得分:2)

你听说过The Law of Demeter吗?

有一个类似的建议(来自Sutter我认为):不要分享对你内部的引用

两者都是为了避免耦合,因为通过共享对内部的引用,这意味着您的公共接口泄漏了实现细节。

现在说了,你的界面不起作用。

问题是您正在锁定代理,而不是对象:我仍然可以通过多个路径访问:

    来自Foo
  • ,不需要互斥锁 - > oups?
  • 来自两个不同的locked_object - >这似乎并不是故意的......

更重要的是,一般来说,你不能锁定一个对象的一个​​部分,因为那样你就不能拥有整个对象的事务语义。

答案 2 :(得分:2)

这是多线程的核心问题,您不能要求客户端代码以线程安全的方式使用您的对象。你真的无法做任何事情来帮助客户端代码陷入成功之中,它必须自己处理锁定。将使代码工作的责任放在最不可能做到正确的人身上。

您可以通过从访问者返回对象的副本来简化操作。这是线程安全的,一个线程只有一个副本。您可能应该使该副本的类型不可变,以便重新强制修改对象不太可能具有所需的结果。一个可能无法解决的无法解决的副作用是这个副本根据定义是陈旧的。这些是可能弊大于利的创可贴。

记录方法中的填充,以便客户端程序员知道该怎么做。

答案 3 :(得分:1)

这不是特别安全。没有什么能阻止用户解决问题:

locked_object<T> o(...);
T* t = o.get();
o.unlock();
t->foo(); // Bad!

当然,很容易理解为什么上面的代码很糟糕,但真正的代码要复杂得多,而且在发布锁定之后,指针可以通过很多方式来解决,这些方法很难确定。