多线程 – Delphi:为什么VCL不是线程安全的?怎么会这样?

到处都注意到VCL不是线程安全的,我们必须同步访问它.所以它的VCL错误不是线程安全的.

VCL本身如何是线程安全的?

最佳答案
具体而言,“线程安全”对你意味着什么?别人怎么样?每当我看到这一点,它最终都会沸腾到这样:“我希望VCL是线程安全的,所以我不必考虑线程和同步问题.我想把我的代码写成好像它仍然是单线程“.

无论在制作VCL所谓的“线程安全”方面做了多少工作,总会遇到可能遇到麻烦的情况.你会如何使它成为线程安全的?我并不是说这是好斗的,而是我只是想证明这不是一个简单的问题,而是一个简单的“全包工作”解决方案.为了突出这一点,让我们来看看一些潜在的“解决方案”.

我看到的最简单和最直接的方法是每个组件都有某种“锁定”,比如互斥或临界区.组件上的每个方法都会在输入时获取锁定,然后在退出之前释放锁定.让我们用thought experiment继续沿着这条路走.考虑一下Windows如何处理消息:

主线程从消息队列中获取消息,然后将其分派给相应的WndProc.然后将此消息路由到适当的TWinControl组件.由于组件具有“锁定”,因此当消息被路由到组件上的适当消息处理程序时,将获取锁定.到现在为止还挺好.

现在采取众所周知的按钮单击消息处理.现在调用OnClick消息处理程序,它很可能是拥有TForm的方法.由于TForm后代也是TWinControl组件,因此现在可以在处理OnClick处理程序时获取TForm的锁.现在按钮组件被锁定,TForm组件也被锁定.

继续这种思路,假设OnClick处理程序现在想要将项添加到列表框,列表视图或其他一些可视列表或网格组件.现在假设一些其他线程(不是主UI线程)已经在访问这个相同的组件.一旦从UI线程调用列表上的方法,它将尝试获取锁,但由于另一个线程当前正在持有锁,因此它无法获取锁.只要非UI线程长时间不持有该锁,UI线程将仅在短时间内阻塞.

到目前为止一切都那么好吧?现在假设,当非UI线程持有列表控件的锁时,会调用通知事件.因为,它很可能是拥有TForm的方法,在进入事件处理程序时,代码将尝试获取TForm的锁.

你看到了这个问题吗?还记得按钮OnClick处理程序吗?它已经在UI线程中有了TForm锁!它现在被阻塞,等待非UI线程拥有的列表控件上的锁定.这是一个经典的死锁.线程A保持锁A并尝试获取由线程B保持的锁B.线程B同时尝试获取锁A.

显然,如果每个控件/组件都有一个自动获取并为每个方法释放的锁定不是解决方案.如果我们将锁定留给用户怎么办?你看到这也不能解决问题吗?您如何确定您拥有的所有代码(包括任何第三方组件)是否正确锁定/解锁控​​件/组件?这如何防止上述情况发生?

整个VCL的单个共享锁怎么样?在这种情况下,对于处理的每条消息,无论消息路由到哪个组件,都会在处理消息时获取锁定.再次,这如何解决我上面描述的类似场景?如果用户的代码添加了其他锁以与其他非UI线程同步,该怎么办?即使是非UI线程终止的简单阻塞行为,如果在UI线程持有VCL锁定时完成,也会导致死锁.

那么非UI组件呢?数据库,串口,网络,容器等……?他们该如何处理?

正如其他答案所解释的那样,Windows已经做了相当不错的工作,正确地将UI消息处理分离到仅创建每个HWND的线程.事实上,准确了解Windows如何在这方面工作将有助于理解如何编写代码以使用Windows和VCL,以避免上面提到的大多数陷阱.底线是编写多线程代码很难,需要相当剧烈的心理转变,以及大量的练习.尽可能多地从多个线程读取尽可能多的线程.用任何语言学习和理解尽可能多的“线程安全”代码编码示例.

希望这是有益的.

转载注明原文:多线程 – Delphi:为什么VCL不是线程安全的?怎么会这样? - 代码日志