数据竞赛是关于事件年表的,甚至不是特定于代码中的语句,请考虑以下(单线程)JavaScript:
let fs = require('fs');
let dirContents = fs.readdirSync('./');
let files = dirContents.map(fname => fs.readFileSync(fname, 'utf-8'));
此代码包含数据竞争,不是因为代码本身做了任何事情,而是因为在我们(顺序)开始读取第一个文件和时间之间,可能有其他程序出现并删除了列表中的最后一个文件我们尝试阅读最后一个。代码看似很短,但在第 2 行和第 3 行的引擎盖下仍然存在命令式迭代。
而且 JavaScript 尽管是单线程的,但它充满了并发性,上述fs函数之所以在名称中包含“同步”,是因为默认版本是异步的,并且在 JavaScript 中很容易在代码本身中引入竞争条件。所以线程是一条红鲱鱼,它们只是使代码中存在数据竞争的可能性更大,它们的缺席证明不了任何事情。
只有两种* 方法可以解决此问题。一种是使用资源锁来尝试确保事件以预期的顺序发生。另一个是确保任何地方都不会发生任何变化。这就是函数式编程方法:诸如不可变数据之类的概念,整个程序是一个大表达式而不是一系列步骤等。
*其实还有另一种方式,可以构造程序正确性的形式证明。这种方法有些不切实际,至少在计算机科学史上的这个特定时刻是这样。