从多个线程对共享对象的同步方法调用

Synchronizing method calls on shared object from multiple threads

本文关键字:对象 同步方法 调用 共享 线程      更新时间:2023-10-16

我正在考虑如何实现一个将包含私人数据最终通过方法调用来修改的私有数据的类。对于同步(使用Windows API),我计划使用CRITICAL_SECTION对象,因为所有线程都将从同一过程中产生。

给定以下设计,我有几个问题。

template <typename T> class Shareable
{
private:
    const LPCRITICAL_SECTION sync; //Can be read and used by multiple threads
    T *data;
public:
    Shareable(LPCRITICAL_SECTION cs, unsigned elems) : sync{cs}, data{new T[elems]} { }
    ~Shareable() { delete[] data; }
    void sharedModify(unsigned index, T &datum) //<-- Can this be validly called
    //by multiple threads with synchronization being implicit?
    {
        EnterCriticalSection(sync);
        /*
            The critical section of code involving reads & writes to 'data'
        */
        LeaveCriticalSection(sync);
    }
};
// Somewhere else ...
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
    Shareable<ActualType> *ptr = static_cast<Shareable<ActualType>*>(lpParameter);
    T copyable = /* initialization */;
    ptr->sharedModify(validIndex, copyable); //<-- OK, synchronized?
    return 0;
}

我看到的方式将在当前线程的上下文中进行API调用。也就是说,我认为这就像我从指针中获取了关键部分对象,并从ThreadProc()中称为API。但是,我担心如果对象是创建对象并将其放置在主/初始线程中,那么API调用会时髦。

  1. sharedModify()在同一对象上称为同时称为从多个线程中,同步将是隐式的吗我上面描述了它?
  2. 我应该得到一个指针关键部分对象并使用它?
  3. 还有其他更适合这种情况的同步机制?

当共享modify()在同一对象上同时调用,从多个线程中,同步是否是隐含的,以我上面描述的方式?

这不是隐含的,它是明确的。只有CRITICAL_SECTION,一次只能容纳一个线程。

我应该得到一个指向关键部分对象的指针并使用它吗?

否。没有理由在这里使用指针。

是否还有其他一些同步机制更适合这种情况?

很难不看到更多代码,但这绝对是"默认"解决方案。这就像一个单连锁的列表 - 您首先学习,它总是有效的,但并不总是最好的选择。

当同一对象上的 sharedModify()在同一对象上称为,从多个线程中,同步会隐含,以我上面的描述?

从呼叫者的角度隐含的,是的。

我应该得到一个指向关键部分对象的指针并使用它吗?

否。实际上,我建议给出Sharable的对象所有权,而不是从外部接受一个(并接受Raii概念来编写更安全的代码),例如:

template <typename T>
class Shareable
{
private:
    CRITICAL_SECTION sync;
    std::vector<T> data;
    struct SyncLocker
    {
        CRITICAL_SECTION &sync;
        SyncLocker(CRITICAL_SECTION &cs) : sync(cs) { EnterCriticalSection(&sync); }
        ~SyncLocker() { LeaveCriticalSection(&sync); }
    }
public:
    Shareable(unsigned elems) : data(elems)
    {
        InitializeCriticalSection(&sync);
    }
    Shareable(const Shareable&) = delete;
    Shareable(Shareable&&) = delete;
    ~Shareable()
    {
        {
        SyncLocker lock(sync);
        data.clear();
        }
        DeleteCriticalSection(&sync);
    }
    void sharedModify(unsigned index, const T &datum)
    {
        SyncLocker lock(sync);
        data[index] = datum;
    }
    Shareable& operator=(const Shareable&) = delete;
    Shareable& operator=(Shareable&&) = delete;
};

是否还有其他一些同步机制更适合这种情况?

取决于。多个线程会同时访问相同索引吗?如果没有,那么根本不需要关键部分。一个线程可以安全访问一个索引,而另一线程访问不同的索引。

如果多个线程需要同时访问相同的索引,则关键部分可能仍然不是最佳选择。如果您只需要一次锁定部分,则锁定整个阵列可能是一个很大的瓶颈。诸如互锁的API或细长的读/写锁之类的东西可能更有意义。这确实取决于您的线程设计以及您实际试图保护的内容。