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

Posted

技术标签:

【中文标题】从多个线程同步对共享对象的方法调用【英文标题】:Synchronizing method calls on shared object from multiple threads 【发布时间】:2016-11-29 22:22:36 【问题描述】:

我正在考虑如何实现一个包含私有数据的类,这些私有数据最终将由多个线程通过方法调用进行修改。对于同步(使用 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) : synccs, datanew 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 调用会有些奇怪。

    sharedModify()同时在同一个对象上调用时, 从多个线程中,同步是否是隐式的,在 我上面描述的方式? 我是否应该获得指向 临界区对象并改用它? 还有其他的吗 更适合这种情况的同步机制?

【问题讨论】:

【参考方案1】:

当从多个线程同时对同一个对象调用 sharedModify() 时,同步是否会像我上面描述的那样是隐式的?

这不是隐含的,而是明确的。只有CRITICAL_SECTION,而且一次只能持有一个线程。

我应该获取一个指向临界区对象的指针并使用它吗?

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

还有其他更适合这种情况的同步机制吗?

不看更多代码很难说,但这绝对是“默认”解决方案。它就像一个单链表——你先学会它,它总是有效的,但它并不总是最好的选择。

【讨论】:

【参考方案2】:

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;
;

还有其他更适合这种情况的同步机制吗?

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

如果多个线程需要同时访问同一个索引,临界区可能仍然不是最佳选择。如果您一次只需要锁定数组的部分,那么锁定整个 数组可能是一个很大的瓶颈。诸如 Interlocked API 或 Slim Read/Write 锁之类的东西可能更有意义。这实际上取决于您的线程设计以及您实际尝试保护的内容。

【讨论】:

以上是关于从多个线程同步对共享对象的方法调用的主要内容,如果未能解决你的问题,请参考以下文章

下个路口见

创建线程有哪几种方式

多线程同步锁的实现方式

穷吉201771010119

线程同步之-旋转门AutoResetEvent

synchronized将任意对象作为对象监视器