我们先来看看一个网友写的 SafeObjectPool :
https://www.cnblogs.com/kellynic/p/9768452.html
从 SafeObjectPool , 我们大概可以自己实现一个 线程池, 比如
var pool = new SafeObjectPool.ObjectPool<Thread>();
Thread thread = pool.Get();
thread.唤醒();
唤醒后, thread 会进入 就绪队列, 很快就可以执行了 。
这样算可以实现一个 线程池 了吧 ?
以下 内容 补充于 2020 年 2 月 28 日 :
我发现 这篇 文章 的 阅读量 在 我的 博客 里 还算是 比较 高 的, 哈哈 。
之前 写过 这篇 文章 后, 我 自己 写了一下 线程池, 但是发现, 自己 实现 线程池 存在 一些问题, 这部分 好像 没有 记录 到 后来 的 博客 里, 所以 就 补充 在 这里 。
自己 实现 线程池, 需要 自己 管理 线程, 比如 用 C# 实现 线程池 的 话, 要 把 Thread 对象 放到 执行队列 和 空闲队列 里, 因为 线程池 对 线程 的 调度 本身 就是 一个 并发环境 下 的 操作, 所以, 对 队列 的 操作 需要 同步(Lock) , 然而, 如果用 C# 的 lock 关键字(Monitor.Enter() ) 的 话, 这是 使用 操作系统 的 lock 原语, 性能不算太高 。
当然, 可以 用 System.Collections.Concurrent 里 的 ConcurrentQueue<T> , System.Collections.Concurrent 里 的 集合 的 并发同步 可能是 用 CAS 指令 实现 的 Lock , 如果 是 这样 , 性能 就 很高, 性能 应该 是 是 C# lock 关键字 的 10 倍 。
但 即使 这样, 操作系统 内核 自身 对 线程 的 调度 的 效率 应该 更高 。
另外一个 问题 是, 在 C# 里, 可以调用 Thread 的 Suspend() 、 Resume() 等 方法 把 Thread 挂起 和 恢复 等, 但, 这些 方法, 最终 是要 通知 操作系统 , 由 操作系统 完成 线程状态 的 切换, 所以, 当 调用了 Suspend() 方法 完成时, 在 操作系统 层面, 该 线程 已经 进入 了 挂起 状态 ? 或者 是, 调用了 Suspend() 方法 , 仅仅是 发了一个 请求 告诉 操作系统 要 把 这个 线程 挂起, 这个 请求 在 操作系统 里 排队 执行, 所以, 调用了 Suspend() 方法 完成时 , 并 不能 确定 在 操作系统 层面 , 该 线程 是否 已经 挂起 ? 又或者 是 其它 的 情况 ?
不知道 。
所以, 在 C# 层面, 调用了 Suspend() 方法, 如果 在 操作系统 层面 该 线程 还没有 进入 挂起 状态, 而 因为 有 新的 任务 添加到 线程池, C# 又 接着 调用了 Resume() 方法, 此时 会 如何 , 会否 报错 , 或者 发生 其它 意想不到 的 事 ?
所以, 在 C# 层面 对 线程 进行 管理 , 又 涉及 到 并发(多个 线程 向 线程池 并发 的 添加 任务), 这个 管理 是否 安全 可靠 ? 不知道 。
所以, 看起来, 还是 操作系统 自身 提供 线程池, 才是 高效 、安全 的 方式 。
调度线程 是 操作系统 内核 的 核心工作, 操作系统 本身 就会 维护 就绪队列 和 挂起队列, 所以, 在 操作系统 之外, 由 语言(应用程序) 自己 再来 维护 一个 执行队列 和 空闲队列 是否 是 重复 的 或者 多此一举 ?
我在 QQ 群 里 听 网友 说, C# 团队 本来 也想 自己 实现一个 线程池, 但是 因为 效率 不高, 所以 放弃了, 还是使用 Windows 线程池 。
在 线程池 之后, 我 研究 过 协程, 也就是 Goroutine , Coroutine 之类, 我当时想 要 实现 协程 需要 语言 自己 实现一个 线程池, 但 上面 又说 线程池 不适合 语言 自己 实现, 这是怎么回事 ? 需要 把 之前 的 想法 整理一下 。
2021-08-27 补充 :
前不久, 5 月份吧, 我写了一个 线程池 : https://github.com/kelin-xycs/EasyThreadPool 。