SAM*_*Pro 1 delphi multithreading vcl thread-safety
到处都注意到VCL不是线程安全的,我们必须同步访问它.所以它的VCL错误不是线程安全的.
VCL本身如何是线程安全的?
All*_*uer 14
确切地说,"线程安全"对你意味着什么?别人怎么样?每次我看到这一点,它最终都会沸腾到这样:"我希望VCL是线程安全的,所以我不必考虑线程和同步问题.我想把我的代码写成好像它仍然是单线程".
无论在制作VCL所谓的"线程安全"方面做了多少工作,总会有一些情况会让你陷入麻烦.会如何,你去使它线程安全的?我不是说这是好斗的,而是我只是想证明一个简单的"全包工作"解决方案不是一个简单的问题.为了突出这一点,让我们看看一些潜在的"解决方案".
我看到的最简单和最直接的方法是每个组件都有某种"锁定",比如互斥或临界区.组件上的每个方法都会在输入时获取锁定,然后在退出之前释放锁定.让我们通过思考实验继续沿着这条道路前进.考虑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,以避免上面提到的大多数陷阱.底线是编写多线程代码很困难,需要相当剧烈的心理转变,以及大量的练习.尽可能多地从多个线程读取尽可能多的线程.用任何语言学习和理解尽可能多的"线程安全"代码编码示例.
希望这是有益的.
VCL不是线程安全的.它是Win32的包装器.Win32是线程安全的,但具有为该语句赋予意义的线程规则.最具体地说,窗口与创建它的线程具有亲缘关系.
Windows消息队列的设计意味着几乎总是希望主线程创建所有GUI窗口.VCL设计师认为只支持这种操作模式是合理的.因此必须从主线程执行所有VCL代码.
改变这一点没有什么可以做的.这是设计的.如果您希望执行VCL代码,则必须在主线程上执行.使用TThread.Synchronize或TThread.Queue安排.
VCL(尤其是UI控件)不是线程安全的原因有很多.
消息输入的竞争条件,特别是在直接调用TControl.Perform()/ TObject.Dispatch()而不是使用PostMessage()/的代码中SendMessage().前者不执行控件的消息处理程序的任何同步,但后者确实如此.因此,从主线程外部执行基于非HWND的消息是不安全的.
HWND具有线程关联性.它只接收和处理消息,并且只能在创建它的线程上下文中销毁.A TWinControl可以在其生命周期内随时甚至多次破坏并重建其HWND.该TWinControl.Handle属性的getter创建一个新的HWND如果没有还不存在.因此,如果控件在另一个线程从Handle属性读取时正在重新创建其HWND ,则控件最终可能会在错误的线程上下文中创建一个新的HWND,从而使控件不再响应主消息循环(并且可能潜在泄漏第二个HWND).因此TWinControl.Handle从主线程外部读取属性是不安全的.
VCL有一个MakeObjectInstance()函数,它创建一个动态代理,允许将TWndMethod类方法用作Win32 WNDPROC窗口回调过程.所有TWinControl控件和一些实用程序类都TTimer使用此函数.在内部,它维护一个全局链接的代理列表,并且该列表不受跨线程的并发访问的保护.因此,从主线程外部创建/销毁基于HWND的VCL控件是不安全的.
我确定还有其他原因,但这些都是重要原因.