混淆了的工作单元和线程

许多服务器端的应用和许多桌面应用都含有与一个特殊任务的执行有关的数据。常见的解决方案是将这类数据放到线程本地存储区中,将变量中的数据与其执行线程相绑定。的确很方便,但这是一个基于有缺陷的假设的实践方案。

\

Bob Martin写了一篇关于假设线程跟工作单元存在一对一关系的专题文章:

\
ThreadLocal变量对一个给定的线程而言是一个极为便利的关联数据的方式。例如,类似于Hibernate这样的框架利用它来保存会话信息。但是,这种practice非常依赖于将一个线程和一个工作单元等同视之。这是一种错误的假设。
\

ThreadLocal是一个Java术语,但其构造在多线程环境中是通用的。Bob还记得:

\
十三年前写我的第一本书时,我和Jim Coplien针对线程和对象的本质发生了争论。他做出了一个令我如今仍记忆犹新的澄清,他说:“一个对象是功能的抽象,一个线程是调度的抽象”。
\

当前,将工作单元中的数据映射到一个线程中是一个标准的模式,一个在许多流行的框架中可以发现的模式,但即便是这种方法存在了相当长的时间,这个不完美的抽象仍然缺乏一些情景。

\

在一个任务的不同部分存在不同的优先级是很常见的事情,而且也没有规则约束说一个任务必须是单线程的。Bob举例说明一个工作单元在基于得到的数据执行一个相当长时间的计算时,可能会非常需要跟一个外部服务的响应性沟通,这样一个问题通常是分解成两个线程来解决。他问道:

\
工作单元相关的变量应该放在哪儿?它们不应被保存到ThreadLocal 中,因为任务的每一个部分在一个分离的线程中运行.它们也不能被保存在静态变量中,因为有很多的线程。最终答案是它们必须在线程间作为栈中的功能参数进行传递,并且被记录在存放于queue队列之中的数据结构内。
\

TapsaKoo不久前也遇到了一个同样的情况。当在WinForms中努力尝试领域驱动的方式时,他描述了他寻找一个保存会话专有数据的问题

\
如果应用程序每次只有一个表单被打开,我会把会话对象保存到Callcontext。如果应用每次打开多个表单,并且这些表单希望拥有我的session类的一个单独的实例时该怎么办?CallContext已经不能满足要求了。那么全都是线程专有的备选方案吗?还剩下什么了?什么也没有剩下?我已经不是考虑这个问题的第一个人了。可能会有存在一个解决方案,但我找不到它。我应该把会话对象注入达到需要它的每个对象实例中吗?还是我将领域类的许多行为重构到服务中,然后将会话对象注入其中?我不太喜欢这种方式,因为我希望我的类被数据容器有更多的含义。
\

在阅读完Bob大叔的回复后,TapsaKoo也同意对此问题没有轻松的解决方案

\
无论你有1个还是10个线程,问题总是相同的。工作单元或者会话状态应该存放在一个不依赖于线程的地方。认为工作单元直接对应于一个单个的线程是非常危险的假设。这种假设会严重限制你的其他的架构性选择。
\

Bob推断可能丢失了某些东西:

\
所以,尽管是司空见惯,ThreadLocal变量混淆了调度与功能的分离问题。它们诱惑我们将功能和调度耦合在了一起。这是非常不幸的事情,因为功能和调度的对应是脆弱的、偶然的。我们实际上应该做的是让建立一个UnitOfWorkLocal变量成为可能。
查看英文原文: Confusing unit-of-work with threads
译者简介:孙向晖,儿子小名“豆豆”,常被人称为“豆豆他爹”。1998年开始步入IT行业,现任浪潮软件质保中心副主任。专注于研究和实践MDA/UP/UML/SCM等相关技术在团队中的大规模应用,对产品化的软件项目管理、需求管理和配置管理略有心得。他的博客为 http://blog.youkuaiyun.com/xiaosun/。参与InfoQ中文站内容建设,请邮件至 china-editorial[at]infoq.com
<think>嗯,用户想知道进程线程在任务分配方面的区别实现方式。首先,我需要回忆一下进程线程的基本概念。进程是操作系统分配资源的基本单位,每个进程都有独立的内存空间系统资源,而线程是进程内的执行单元,共享同一进程的资源。这应该任务分配有关。 接下来,任务分配机制。进程的任务分配可能需要更多的系统资源,比如独立的地址空间,而线程因为共享资源,可能更适合处理需要频繁通信的任务。用户提到的引用[1][3]都提到进程用于资源分配调度,线程用于具体操作,并发执行。需要结合这些点来解释差异。 实现方式方面,进程的创建销毁开销大,线程则较轻量。引用[2]提到Python中进程适合CPU密集型任务,线程适合I/O密集型任务。这可能是因为线程切换更快,适合需要等待I/O的操作,而进程能利用多核CPU进行并行计算。 用户可能还关心实际应用中的选择,比如什么时候用进程,什么时候用线程。需要举例说明,比如Web服务器处理请求时用线程,科学计算用多进程。同时,线程间的通信更简单,但需要处理同步问题,进程间通信则需要通过IPC机制,比如管道、消息队列等。 另外,引用[3]指出进程的并发性是基于操作系统调度,而线程的并发可以在进程内部。这可能涉及到多核处理器上的并行执行,进程可以在不同核心上运行,线程同样可以,但因为共享内存,同步机制更复杂。 还要注意用户的问题中的任务分配机制差异,可能需要分点比较,比如资源分配、通信方式、创建开销、适用场景等。确保回答结构清晰,符合用户要求的逐步解决问题,并引用相关文献。 可能用户的实际需求是在编程中选择合适的并发模型,所以需要强调根据任务类型选择进程或线程。例如,Python中的GIL限制线程在CPU密集型任务中的效率,所以多进程更适合这类情况。而I/O密集型任务由于存在等待时间,线程的切换开销小,更适合多线程。 最后,确保使用正确的中文表达,避免术语混淆,并自然引用提供的参考文献。生成的相关问题可能涉及如何选择进程或线程,或者具体的实现例子,比如在Python中如何创建多进程线程。</think>### 进程与线程的任务分配机制及差异分析 #### 一、任务分配机制对比 1. **进程的任务分配** 进程是操作系统进行资源分配的最小单位,每个进程拥有独立的虚拟地址空间、文件描述符等资源。当程序需要执行多个独立任务时,操作系统会创建多个进程,通过进程间通信(IPC)实现协作。例如: - **独立计算任务**:如同时运行浏览器代码编辑器,两者通过进程隔离资源[^1][^3]。 - **多核并行计算**:使用多进程实现 CPU 密集型任务的负载均衡(如科学计算)[^2]。 2. **线程的任务分配** 线程是进程内的执行单元,共享进程的代码段、堆内存文件资源。多线程通过共享内存实现高效协作,适用于需要频繁交互的任务: - **I/O 密集型任务**:如 Web 服务器同时处理多个请求,线程共享网络连接缓存。 - **用户界面响应**:GUI 程序使用主线程更新界面,后台线程处理数据[^3]。 #### 二、核心差异总结 | **特性** | **进程** | **线程** | |----------------|-----------------------------------|-----------------------------------| | **资源隔离** | 独立内存空间、文件句柄等 | 共享进程资源 | | **创建开销** | 高(需复制父进程资源) | 低(仅需分配栈寄存器) | | **通信成本** | 需 IPC(管道、消息队列等) | 直接共享内存(需同步机制) | | **适用场景** | CPU 密集型、独立任务 | I/O 密集型、高交互任务 | | **崩溃影响** | 单进程崩溃不影响其他进程 | 单线程崩溃可能导致整个进程终止 | #### 三、实现方式示例(Python) ```python # 多进程实现(适合独立计算) from multiprocessing import Process def cpu_intensive_task(): sum(range(10**7)) p1 = Process(target=cpu_intensive_task) p2 = Process(target=cpu_intensive_task) p1.start(); p2.start() # 多线程实现(适合I/O操作) import threading def io_task(): requests.get("https://api.example.com") t1 = threading.Thread(target=io_task) t2 = threading.Thread(target=io_task) t1.start(); t2.start() ``` #### 四、选择建议 - **优先使用线程**:当任务需要共享数据(如缓存)或频繁等待外部响应时(如网络请求)。 - **必须使用进程**:当任务需要规避全局解释器锁(GIL)限制(如 Python 中的并行计算),或需要高容错性时[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值