MySql.Data with 'new Thread' vs ThreadPool:使用 ThreadPool 创建死锁

Posted

技术标签:

【中文标题】MySql.Data with \'new Thread\' vs ThreadPool:使用 ThreadPool 创建死锁【英文标题】:MySql.Data with 'new Thread' vs ThreadPool: using ThreadPool creates a deadlockMySql.Data with 'new Thread' vs ThreadPool:使用 ThreadPool 创建死锁 【发布时间】:2021-12-25 08:50:04 【问题描述】:

在一个项目中,我发现了一种特殊的行为,然后使用 ThreadPool 中的线程连接到 mysql 实例;应用程序冻结了,看起来这是由于MySqlConnection.Open 内部的死锁。当我手动创建线程并运行 Start 方法时,不会发生这种情况。

This project on GitHub 展示了这一点,我想帮助理解它为什么会这样。

有两种方法,TestThreadsTestTask。每个方法执行一个创建/生成/获取 200 个线程的 for 循环,每个线程连接到具有标准 MySqlConnection 类的 MySQL 8 服务器,休眠 5 秒,然后关闭连接。秒表测量每个方法完成的时间。 TestThreads 手动创建线程 (new Thread()... thread.Start());在大约 12-14 秒内完成,我没有发现任何问题。 TestTask 使用ThreadPool.QueueUserWorkItem;由于MySqlConnection.Open 内部似乎发生了死锁,它通常根本不会完成

这是运行TestTask的截图:

所有线程都在MySqlConnection.Open 方法中,并停留在那里。他们似乎没有进一步打印一些日志消息 - 没有为我运行 TestTask 生成日志消息。

另一方面,运行 TestThreads 有效,然后我得到我期望的日志消息:

我想了解为什么创建手动线程时 ThreadPool 会失败?为什么 MySqlConnection 在一种情况下会死锁,而在另一种情况下不会?

请注意,由于我们是针对同一个 MySQL 服务器进行测试,因此这不是 MySQL 配置问题。我也看不出它怎么可能是线程池饥饿;我们将 MinThread 设置为 200。另外,由于在 TestTask 的情况下我从来没有得到一个 Console.WriteLine,肯定还有别的东西吗?

【问题讨论】:

可能与饥饿有关,因为 ThreadPool 增长速度有限,以及如何处理任务。这可以通过将最小线程池大小设置为较大并查看行为是否/如何变化来验证。 labs.criteo.com/2018/10/… 此测试应用程序将 minthreads 设置为 200,但在其他情况下,我尝试将其设置为 1000,没有区别。我在测试项目中将值调整为500,没有区别。 你检查过阻塞线程的堆栈跟踪(包括外部代码)吗?您将 min 和 max 设置为 200,如果其他一些代码(可能是 mysqlconnection 或相关的东西)也想要启动任务或线程池线程怎么办? 【参考方案1】:

MySql.Data 里面有一些写得很糟糕的异步代码(除了 problem 之外,没有一个 Async 方法实际上是异步的)。

例如,此代码使用Task 启动异步工作,然后调用Task.Wait:https://github.com/mysql/mysql-connector-net/blob/4306c8484ec74b3ee1c349847f66acabbce6d63c/MySQL.Data/src/common/StreamCreator.cs#L98-L100

这是“异步同步”,即well-known cause of deadlocks。问题很可能是线程池被你的用户代码阻塞了,所以完成TcpClient.ConnectAsync所需的工作无法排队,所以Tasks没有一个可以完成,所以没有代码可以前进。

您可以通过强制线程池拥有更多线程来解决这个问题(这在我的机器上有效,但它可能不是一个有保证的解决方案):

ThreadPool.SetMinThreads(500, 500);
// do NOT call ThreadPool.SetMaxThreads

您还可以切换到MySqlConnector,它可以正确实现线程代码,并且不需要SetMinThreads 解决方法即可运行。我添加了一个使用 MySqlConnector 的异步方法 would be like 的示例。 (披露:我是 MySqlConnector 的首席开发人员。)

【讨论】:

感谢您的意见!听起来很正确,问题是 Mysql.Data 中的一个错误。但是,它不会改变示例项目中增加 MinThreads 的任何内容,我之前尝试过,但没有任何变化。我将用 MySqlConnector 替换 Mysql.Data! ? @Ted 我下载并在我的机器上运行代码,并使用ThreadPool.SetMinThreads(500, 500)(并删除ThreadPool.SetMaxThreads)停止了挂起。这绝对是一个 hacky 解决方法,所以对我来说假设它总是有效可能很危险。 啊,我明白了。感谢更新。我认为最好的方法是用 MySqlConnector 替换 Mysql.Data,所以明天我会这样做,看看效果如何??

以上是关于MySql.Data with 'new Thread' vs ThreadPool:使用 ThreadPool 创建死锁的主要内容,如果未能解决你的问题,请参考以下文章

How To Move a MySQL Data Directory to a New Location on Ubuntu 16.04

WARNING: The host 'WeiLei' could not be looked up with resolveip.

为啥 Vec::with_capacity 比 Vec::new 对于小的最终长度慢?

html brands_with_facets_bugfix_new.html

[Journey with golang] 0. Planning for the New year and new journey

php register_new_post_type_with_taxonomy.php