多线程占用修改同一excel文件冲突_<1%修改量,让单线程代码完美并行

众所周知,多线程开发要处理各种同步和竞争问题,一不留神就会原地爆炸。

那么问题来了,如果手头有一份成品单线程代码,如何让它支持多线程并行?

本文将介绍一个最为简单粗暴的方式,重构改动1%以下,几乎不会引入任何问题,并且在目标场景下可以完美并行。

目标场景

将这份代码当做黑盒使用,不强求内部多线程,而是将输入输出当做一个任务,多个任务之间可以并行。

问题分析

该目标场景是最简单的并行场景了,理论上没任何数据竞争,多个任务之间的关系就和多个进程一样完全隔离的。

从“输入——执行——输出”这条链条来看,包装为任务的方式,输入输出先天就隔离开来,不存在数据竞争,数据竞争主要是执行过程中的内部状态,也就是副作用。

这不还是废话么,又绕回开头了。

不不不,仔细想想,引起副作用的内部状态是怎么来的?

原因很简单,就是内部无上下文封装,所有执行共享一个上下文,因此并行场景存在竞争。

而这些内部共享的上下文是怎么存在的呢?从编码角度来看,必然是静态变量。

解决方案

问题分析到这里,解决方案已经相当清晰了,让这些静态变量每次执行时都使用独立实例就行,这样上下文/状态自然就隔离开了。

要做到这一点,无需要重构数据结构和代码逻辑,只需要引入c++11的一个关键字——thread_local,在所有静态变量的声明处加上这个关键字即可。

thread_local关键字确保了该对象在每个线程上都有一个副本。然后我们再让每个任务都在一个线程里执行,问题就解决了。

残留问题

该手段下,每次执开启一个新线程和直接复用线程池,存在一定的差异。但实际运行结果,在绝大多数场景下应该是没区别的。

如果复用线程的话,在同一个线程里多次执行,内部状态还是沿用上次的,和修改前顺序调用多次一样。

此时内部状态是否会导致多次运行结果不一致,需要看目标代码本身的逻辑。

但考虑到修改之前的单线程代码,本来就是顺序执行多次,每次都沿用上一次的内部状态,所以理论上和修改后在线程池的运行结果不会存在差异。

示范案例

我手头有一个业务需求,将一组二进制数据文件转为excel表格导出。

工具库用的QtXlsx,该库是单线程设计,未对多线程做考虑。

实际表现是:

  • 生成数据对象过程中可以并行,数据内容包含在独立的c++对象中。
  • 保存至xlsx时,因为xlsx有数据复用的机制,相同的内容会引用同一个数据对象,因此库内部使用静态字典来整理这些复用的数据。
  • 二进制文件读取到生成xlsx内存对象耗时大约24s(秒表计时),多任务可以并行。
  • 保存文件过程无法并行,通过加锁进行同步,每个文件保存需要14s(手掐秒表)。
  • 因此总耗时为24 + 14*4 = 80s。

全文搜索代码中的关键字,大约有十多个static对象。无脑给所有对象都加上thread_local标注后,编译运行:

  • 总耗时38s(手动秒表);
  • 38 = 24 + 14,符合设想结果,完美实现并行。

为确认并行计算并未出错,进行如下验证:

  1. 将二进制文件复制四份;
  2. 用互斥锁同步版批量导出四个文件;
  3. 用完美并行版批量导出四个文件;
  4. 将八个xlsx表格用7zip解压(-x的office文件本质是zip压缩包,包含xml格式+数据文件,文本表格的数据文件也是纯文本);
  5. 用diff对比八组xml和文本文件。

对比结果完全相同,说明本重构方案无bug,以上。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值