MySql.Data 与 'new Thread' 对比 ThreadPool:使用 ThreadPool 会造成死锁
MySql.Data with 'new Thread' vs ThreadPool: using ThreadPool creates a deadlock
在一个项目中,我发现了一个奇怪的行为,然后使用 ThreadPool 中的线程连接到 MySQL 实例;应用程序冻结,看起来是由于 MySqlConnection.Open
内部的死锁。当我手动创建 Thread 和 运行 Start 方法时,不会发生这种情况。
This project on GitHub 展示了这一点,我想帮助理解为什么它会这样。
- 有两种方法,
TestThreads
和TestTask
。每个方法执行一个creates/spawns/fetches200个线程的for循环,每个线程连接一个MySQL8标准的MySqlConnection
class服务器,休眠5秒,关闭连接。秒表测量每个方法完成的时间。
TestThreads
手动创建线程 (new Thread()... thread.Start()
);在大约 12-14 秒内完成,我没有发现任何问题。
TestTask
使用ThreadPool.QueueUserWorkItem
;由于 MySqlConnection.Open
内部似乎发生了死锁,它通常根本不会完成
这是来自 运行ning TestTask
的屏幕截图:
所有线程都在MySqlConnection.Open
方法中,并停留在那里。他们似乎没有进一步打印出一些日志消息 - 没有为我生成任何日志消息 运行ning TestTask
.
另一方面,运行ning TestThreads
有效,然后我得到了我期望的日志消息:
我想了解为什么在创建手动线程时 ThreadPool 会失败?为什么 MySqlConnection 在一种情况下会死锁,而在另一种情况下不会?
请注意,由于我们是针对同一个 MySQL 服务器进行测试,因此这不是 MySQL 配置问题。我也不明白 ThreadPool 饥饿是怎么回事;我们将 MinThread 设置为 200。此外,由于在 TestTask
的情况下我从来没有得到一个 Console.WriteLine,所以肯定还有其他东西吗?
MySql.Data 里面有一些写得不好的异步代码(除了 之外 Async
方法的 none 实际上是异步的)。
例如,此代码使用 Task
开始异步工作,然后调用 Task.Wait
:https://github.com/mysql/mysql-connector-net/blob/4306c8484ec74b3ee1c349847f66acabbce6d63c/MySQL.Data/src/common/StreamCreator.cs#L98-L100
这是“sync-over-async”,即well-known cause of deadlocks。问题很可能是线程池被您的用户代码阻塞了,所以完成 TcpClient.ConnectAsync
所需的工作无法排队,所以 Task
中的 none 可以完成,所以没有代码可以向前推进。
你可以通过强制线程池有更多线程来解决这个问题(这在我的机器上有效,但它可能不是一个有保证的解决方案):
ThreadPool.SetMinThreads(500, 500);
// do NOT call ThreadPool.SetMaxThreads
您也可以切换到MySqlConnector, which implements threading code properly, and doesn't need the SetMinThreads
workaround in order to run. I've added an example of what using MySqlConnector's async methods would be like。 (披露:我是 MySqlConnector 的首席开发人员。)
在一个项目中,我发现了一个奇怪的行为,然后使用 ThreadPool 中的线程连接到 MySQL 实例;应用程序冻结,看起来是由于 MySqlConnection.Open
内部的死锁。当我手动创建 Thread 和 运行 Start 方法时,不会发生这种情况。
This project on GitHub 展示了这一点,我想帮助理解为什么它会这样。
- 有两种方法,
TestThreads
和TestTask
。每个方法执行一个creates/spawns/fetches200个线程的for循环,每个线程连接一个MySQL8标准的MySqlConnection
class服务器,休眠5秒,关闭连接。秒表测量每个方法完成的时间。 TestThreads
手动创建线程 (new Thread()... thread.Start()
);在大约 12-14 秒内完成,我没有发现任何问题。TestTask
使用ThreadPool.QueueUserWorkItem
;由于MySqlConnection.Open
内部似乎发生了死锁,它通常根本不会完成
这是来自 运行ning TestTask
的屏幕截图:
所有线程都在MySqlConnection.Open
方法中,并停留在那里。他们似乎没有进一步打印出一些日志消息 - 没有为我生成任何日志消息 运行ning TestTask
.
另一方面,运行ning TestThreads
有效,然后我得到了我期望的日志消息:
我想了解为什么在创建手动线程时 ThreadPool 会失败?为什么 MySqlConnection 在一种情况下会死锁,而在另一种情况下不会?
请注意,由于我们是针对同一个 MySQL 服务器进行测试,因此这不是 MySQL 配置问题。我也不明白 ThreadPool 饥饿是怎么回事;我们将 MinThread 设置为 200。此外,由于在 TestTask
的情况下我从来没有得到一个 Console.WriteLine,所以肯定还有其他东西吗?
MySql.Data 里面有一些写得不好的异步代码(除了 Async
方法的 none 实际上是异步的)。
例如,此代码使用 Task
开始异步工作,然后调用 Task.Wait
:https://github.com/mysql/mysql-connector-net/blob/4306c8484ec74b3ee1c349847f66acabbce6d63c/MySQL.Data/src/common/StreamCreator.cs#L98-L100
这是“sync-over-async”,即well-known cause of deadlocks。问题很可能是线程池被您的用户代码阻塞了,所以完成 TcpClient.ConnectAsync
所需的工作无法排队,所以 Task
中的 none 可以完成,所以没有代码可以向前推进。
你可以通过强制线程池有更多线程来解决这个问题(这在我的机器上有效,但它可能不是一个有保证的解决方案):
ThreadPool.SetMinThreads(500, 500);
// do NOT call ThreadPool.SetMaxThreads
您也可以切换到MySqlConnector, which implements threading code properly, and doesn't need the SetMinThreads
workaround in order to run. I've added an example of what using MySqlConnector's async methods would be like。 (披露:我是 MySqlConnector 的首席开发人员。)