我在应用多线程加速图像处理(具体参见图像处理的多线程计算)的过程中,曾遇到过一个线程同步的问题。多线程对图像不同子块进行处理,再合成。结果发现最终不是全部子块都处理成功,有的子块好像没有被处理。而且发现结果图像中哪个子块没有被处理好像是随机发生的,没有处理的子块个数也不固定。检查程序,确信所有的子块都处理了,所以怀疑各个子线程计算子块后,在更新图像时线程同步出了问题。
有一个子图没有处理
在对结果图像进行更新是用到了DC的BitBlt(),多线程计算时对图像数据是进行了保护的。在更新结果图像时,原来是这么写的。
CDC* pSrcDC = Src.GetDC();
CSingleLock sLock(&Src.m_Critical, TRUE);
BOOL ret = m_memDC.BitBlt(SrcX, SrcY, nWidth, nHeight, pSrcDC, SrcX, SrcY, SRCCOPY);
sLock.Unlock();
Src.ReleaseDC(pSrcDC);
m_memDC是设备兼容的内存DC,最后要写到显示设备。Src是计算后的完整图像位图实例,Src.GetDC()和Src.ReleaseDC(pSrcDC)包含了把Src选入和选出pSrcDC的操作。各计算子线程会异步对Src的各个子块进行更新。每次BitBlt()也是对要更新的子块进行操作,写到m_memDC,对BitBlt()也已经添加了保护,按理说不应该有问题的。但是其他地方都检查过了,只有这里值得怀疑。只有更新没有成功,才可能出现这种情况了。试着对整个DC操作加上保护,改成这样:
CSingleLock sLock(&Src.m_Critical, TRUE);
CDC* pSrcDC = Src.GetDC();
BOOL ret = m_memDC.BitBlt(SrcX, SrcY, nWidth, nHeight, pSrcDC, SrcX, SrcY, SRCCOPY);
Src.ReleaseDC(pSrcDC);
sLock.Unlock();
这样一改,结果马上就正确了。按原来的想法,Src是作为只读的源来对m_memDC更新的,而且每次只更新一个子块。如果同时有其他的子线程在对Src进行其他子块的更新,也不是这里BitBlt()所用到的目标子块。按理来说,即使这里不加保护,应该也不会对最后的结果有影响的。而我原来的代码也对BitBlt()加了保护,只是没有把位图选入选出DC的操作保护起来。然而,实际结果却不是这样。也就是说,在一个位图被选入DC了以后,即使这个位图是作为源,还没有被读取的时候,如果其他线程对这个位图进行了修改,写操作,这个修改可能丢失。下次再对m_memDC更新这个修改,由于Src中的这个修改已经丢失,自然结果就不对了。所以对这个位图保护要从被选入DC之前就开始。
难道在把图像选入DC(调用SelectObject())时,是把图像拷贝到DC的?因此如果不对位图在被选入之前进行保护,把图像选出DC时,内容从DC拷贝回到图像,有可能把图像中别的线程更新的部分覆盖了。所以只有在把图像选入DC之前就保护起来,才能保证每个线程选入DC的图像都是最新的,同时保证在对m_memDC更新时,别的线程不能更新Src。不过这样吧内存向DC拷来拷去,似乎从效率上来说不太好吧。具体到底如何,还真不清楚了。如果有了解的朋友,还望告知。