本文是关于用于多线程的低级 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。