最近用tbb和qt写了一个延时摄影后期控制镜头的工具,主要就是扫描目录下所有图片,按照给定参数截取图片中某区域并另存,模拟镜头摆动。
扫描后的图片路径保存在qlist内,作为只读数据,交由tbb的parellel_for处理。tbb并行对qlist每个元素内的路径对应的图片进行读取,裁剪,另存操作(磁盘是ssd,这个程序在8线程的机器上,可令cpu满负荷)。理想是美好的,现实是残酷的。
前提,qlist(不光qlist了,qt很多数据结构都)实现了copy on write,qlist的索引操作符分为const和非const(const[] 和 [])
首先,界面拥有路径qlist,并按值传递给tbb处理类。
接着,执行过程中遇到了tbb处理线程崩溃的现象,发现从qlist索引出来的路径是无效的QString,调试分析发现,当线程A对qlist索引时,触发了qlist的copy on wirte机制(虽然是按值传递,但仍然与界面的qlist引用同一份数据),机制在没有执行完成的时后,线程B又对qlist进行索引,所以线程B得到的qlist元素是无效的。避免这种情况发生,就要使用const[] 。
个人感想,copy on write机制对于数据平行运算(data parallel)来说属于坑爹的机制,data parallel一个相当重要的前提是,对容器(同一个或不同的)元素的并发读写不会改变容器自身的状态(虽然对于单个元素来说可能存在竞争,但对于容器来说不存在竞争),最简单就例如C语言,对数组元素的读写不会改变数组状态,又例如std::vector,再高级点就例如GPU对render target,memory object,random access view这类对象的并发操作不会改变对象的状态。而qlist只能提供元素只读,同时对元素读写就可能悲剧了。还好,这个工具只会对qlist元素并发读,不需要读写。注意是qlist内的元素,不是qlist容器并发读。
题外话,处理完这个问题后,又出现另一个问题,调试模式下输出文件数少了,最后发现是malloc返回空指针,erron是12,无法分配内存,悲剧啊,图片文件过大了,线程过多了。我处理的图片分辨率是7000*4000,tbb线程有两个图片对象,一个输入一个输出,机器是8线程的cpu,程序是32位,算了一下,2G内存真有可能不够,特别是调试模式,看来要迁移到64位才行。
qt容器在并发时需要注意的地方