【问题标题】: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) : 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. 还有其他的吗 更适合这种情况的同步机制?

【问题讨论】:

    标签: c++ multithreading winapi


    【解决方案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 锁之类的东西可能更有意义。这实际上取决于您的线程设计以及您实际尝试保护的内容。

      【讨论】:

        猜你喜欢
        • 2016-05-03
        • 1970-01-01
        • 2011-05-21
        • 1970-01-01
        • 2014-01-06
        • 1970-01-01
        • 2023-03-14
        • 1970-01-01
        • 2016-06-02
        相关资源
        最近更新 更多