您无法真正测试线程安全性。您所能做的就是证明您的代码不是线程安全的,但是如果您知道如何做到这一点,那么您已经知道在程序中要做什么来修复该特定错误。问题在于您不知道的错误,您将如何为这些错误编写测试?除此之外,线程问题比其他问题更难发现,因为调试行为已经可以改变程序的行为。从一个程序运行到下一个程序,从一台机器到另一台机器,情况会有所不同。CPU 和 CPU 内核的数量、并行运行的程序的数量和种类、程序中发生的事情的确切顺序和时间——所有这些以及更多都会对程序行为产生影响。[我实际上想将月相之类的东西添加到这个列表中,
我的建议是不要将其视为实现问题,而应开始将其视为程序设计问题。你需要学习和阅读所有你能找到的关于多线程的东西,不管它是不是为 Delphi 编写的。最后,您需要了解基本原理并将其正确应用到您的编程中。诸如临界区、互斥体、条件和线程之类的原语是操作系统提供的,大多数语言只将它们包装在它们的库中(这忽略了诸如 Erlang 提供的绿色线程之类的东西,但这是一个很好的起点)。
我会说从关于线程的维基百科文章开始,然后通过链接的文章开始工作。我从Aaron Cohen 和 Mike Woodring 的“Win32 多线程编程”一书开始——它已经绝版,但也许你可以找到类似的东西。
编辑:让我简要跟进您编辑的问题。所有对非只读数据的访问都需要正确同步才能线程安全,并且对列表进行排序不是只读操作。因此,显然需要围绕对列表的所有访问添加同步。
但是随着系统中的内核越来越多,持续锁定将限制可以完成的工作量,因此寻找一种不同的方式来设计程序是一个好主意。一个想法是在程序中引入尽可能多的只读数据——不再需要锁定,因为所有访问都是只读的。
我发现接口在设计多线程程序时非常有用。接口可以实现为只有对内部数据进行只读访问的方法,如果你坚持使用它们,你可以非常确定很多潜在的编程错误不会发生。您可以在线程之间自由共享它们,并且线程安全的引用计数将确保当对它们的最后一个引用超出范围或被分配另一个值时,它们被正确释放。
您所做的是创建从 TInterfacedObject 派生的对象。它们实现了一个或多个接口,这些接口都只提供对对象内部的只读访问,但它们也可以提供改变对象状态的公共方法。创建对象时,您同时保留对象类型的变量和接口指针变量。这样生命周期管理很容易,因为当异常发生时对象会被自动删除。您使用指向对象的变量来调用正确设置对象所需的所有方法。这会改变内部状态,但由于这只发生在活动线程中,因此没有潜在的冲突。正确设置对象后,您将接口指针返回给调用代码,并且由于除了通过接口指针之外没有其他方法可以访问该对象,因此您可以确定只能执行只读访问。通过使用这种技术,您可以完全消除对象内部的锁定。
如果你需要改变对象的状态怎么办?你没有,你通过从接口复制数据来创建一个新对象,然后改变新对象的内部状态。最后,您将引用指针返回到新对象。
通过使用它,您只需要锁定获取或设置此类接口的位置。通过使用原子交换函数,它甚至可以在没有锁定的情况下完成。有关设置接口指针的类似用例,请参阅Primoz Gabrijelcic 的这篇博客文章。