Teigha 多线程低级 API(第 2 部分,共 4 部分)

本文是关于用于多线程的低级 API 系列文章的一部分。有关第一篇文章,请参阅第 1 部分

内置 Teigha 互斥对象

Teigha Kernel 提供了互斥对象的跨平台实现。有关互斥(MUTual EXclusion)对象的描述,请参阅维基百科

互斥对象封装在 OdMutex 类中。此 OdMutex 类仅包含两个方法:

void lock();
void unlock();

OdMutex::lock 方法可以在临界区的开头调用,例如,该临界区包含可从多个线程访问的共享资源。OdMutex::unlock 方法必须在临界区结束时调用。OdMutex::lock/OdMutex::unlock 方法表示共享资源访问的范围,因此它们应始终成对调用。如果在使用 OdMutex::lock 方法后未调用 OdMutex::unlock 方法,则代码段将无限期地保持锁定状态,并且应用程序将进入无限循环。当线程调用 OdMutex::unlock 方法时,它会为在 OdMutex::lock 方法调用中等待的其他线程打开大门。互斥对象是可重入的,这意味着拥有锁定互斥对象的同一线程可以多次再次进入锁定区域,但当然,在每次后续进入后,它必须调用互斥解锁,这将减少临界区中线程进入的次数;只有最终的 OdMutex::unlock 方法调用才会为其他线程解锁临界代码段。

为了简化互斥对象的使用,可以使用 OdMutexAutoLock 类。它只是在构造函数中调用 OdMutex::lock 方法,在析构函数中调用 OdMutex::unlock 方法;这保证了 lock/unlock 方法对被一起调用,并且开发人员在离开临界区时不会忘记调用互斥锁解锁。

互斥对象使用示例

为了演示互斥对象的使用,我们将扩展本系列文章中第 1 部分的多线程图像处理示例。我们将添加一个新的 ExampleMtStringAccumulator 类,它将多个线程的输出文本字符串累积到一个大的文本字符串中:

// Example of multithread strings accumulator invoking mutex object
class ExampleMtStringAccumulator
{
  protected:
    OdString m_outputString;
    OdMutex m_mutex;
  public:
    ExampleMtStringAccumulator() {}
    ~ExampleMtStringAccumulator() {}
    void addString(const OdString &str)
    {
      TD_AUTOLOCK(m_mutex)
      m_outputString += str;
    }
    const OdString &getString() { return m_outputString; }
};

由于 ExampleMtStringAccumulator::addString 方法将从多个线程调用,为了避免字符串连接过程中线程之间的冲突,我们将使用互斥对象来保护字符串连接操作。TD_AUTOLOCK 宏只是为多线程 Teigha 配置调用 OdMutexAutoLock 类,因此在此处使用它可以简化最终代码。或者,我们可以直接使用 OdMutexAutoLock 类:

OdMutexAutoLock autoLock(m_mutex);

现在我们在主示例函数中构造 ExampleMtStringAccumulator 类的一个实例:

// Multithread output strings accumulator
ExampleMtStringAccumulator stringsAccum;

扩展 ProcessImageCaller 类,通过 ExampleMtStringAccumulator 类输出其处理的扫描行号:

// Thread running method implementation
class ProcessImageCaller : public OdRxObject
{
  OdSmartPtr<ProcessedRasterImage> m_pProcImage;
  OdUInt32 m_scanLineFrom, m_nScanLines;
  ExampleMtStringAccumulator *m_pOutput;
  public:
    ProcessImageCaller *setup(ProcessedRasterImage *pProcImage, OdUInt32 scanLineFrom, OdUInt32 nScanLines)
    { m_pProcImage = pProcImage; m_scanLineFrom = scanLineFrom; m_nScanLines = nScanLines;
      return this; }
    static DWORD WINAPI entryPoint(LPVOID pArg)
    {
      ::odThreadsCounter().startThread();
      ProcessImageCaller *pCaller = (ProcessImageCaller*)pArg;
      pCaller->m_pProcImage->process(pCaller->m_scanLineFrom, pCaller->m_nScanLines);
      pCaller->m_pOutput->addString(OdString().format(OD_T("Thread processed scanlines from %u to %u\n"),
                                    (unsigned)pCaller->m_scanLineFrom, (unsigned)(pCaller->m_scanLineFrom + pCaller->m_nScanLines - 1)));
      ::odThreadsCounter().stopThread();
      return EXIT_SUCCESS;
    }
    ProcessImageCaller *run(SimpleWinThreadsPool &threadPool, ExampleMtStringAccumulator *pOutput)
    {
      m_pOutput = pOutput;
      threadPool.runNewThread(entryPoint, this, ThreadsCounter::kNoAttributes);
      return this;
    }
};

现在我们可以将 ExampleMtStringAccumulator 类实例传递给每个 ProcessImageCaller 实例,并在所有线程完成后将累积的字符串输出到控制台:

// Run threads for raster image processing
const OdUInt32 nScanlinesPerThread = pProcImage->pixelHeight() / 4;
for (OdUInt32 nThread = 0; nThread < 4; nThread++)
{
  OdUInt32 nScanlinesPerThisThread = nScanlinesPerThread;
  if (nThread == 3) // Height can be not divided by 2, so last thread can have one scanline less.
    nScanlinesPerThisThread = pProcImage->pixelHeight() - nScanlinesPerThread * 3;
  lockedObjects.push_back(
    OdRxObjectImpl<ProcessImageCaller>::createObject()->
      setup(pProcImage, nScanlinesPerThread * nThread, nScanlinesPerThisThread)->
        run(winThreadPool, &stringsAccum));
}

// Wait for threads completion
winThreadPool.wait();
lockedObjects.clear();

// Output accumulated string
odPrintConsoleString(stringsAccum.getString());

最终输出将如下所示:

4 个文件加载并渲染,耗时 0.421743 秒
线程处理了从 768 到 1023 的扫描行
线程处理了从 512 到 767 的扫描行
线程处理了从 0 到 255 的扫描行
线程处理了从 256 到 511 的扫描行
最终光栅图像处理耗时 0.583598 秒

请关注即将发布的另一篇文章,内容是关于使用内置的 Teigha 线程 ID 访问器和多线程低级 API。

今天就开始行动

免费试用 ODA 软件 60 天。
无风险,无需信用卡。

免费试用