1、多线程级别的并行计算写多线程应用程序最困难的地方在于如何使各线程的工作协调进行。Windows 提供的用于线程间通信的各种机制是很容易掌握的,可是要把它们应用到工作中完成既定的功能时就会遇到这样、那样的困难。对于常见的“生产者-消费者 ”模型,只要采取合理同步措施实现数据交换的统一性即可。这类模型中的多线程往往任务独立,主要两类线程,一类写线程(生产者),一类读线程(消费者)。但在实际应用中,多核机器往往需要使用多线程来协作处理一项大规模的计算任务,这涉及到并行计算的概念和多核编程技术。如何让多个处理器(多个线程)协作完成一项大规模的任务,涉及到任务的分解和调度。因此,多核编程技术的关键问题在
2、于如何将计算均匀分摊到各个 CPU 核上。并行(Parallel) 计算,即空间复用多个处理器,属于线程级别上的协作。关于多线程协作,参阅王艳平著Windows 程序设计第 3 章Win32 程序的执行单元中的 CRapidFinder 例程。该例程演示了如何使用多线程协助完成文件搜索任务。多进程协助完成任务分布式计算的滥觞分布式计算则是进程级别上的协作,它是一种把需要进行大量计算的工程数据分割成小块,由多台计算机分别计算,在上传运算结果后再统一合并得出数据结论的技术。现代大规模 CG 视觉特效的渲染系统有很多渲染节点组成,采用领先的分布式渲染技术,系统将自动确定网络中可用的渲染节点和资源,同
3、时将将任务分解到相应渲染节点,自动负载平衡功能可以优化工作流程中每个渲染节点的使用效率。从后天到2012,再到 阿凡达,这些大电影,其数以 PB 计艰苦卓绝的渲染工作无不依赖于现代分布式集群工作站的协同作战。线程的池化管理通常情况下,内存的分配和释放通常都是 mallloc 和 free 显式进行的。对同一块内存的多次释放通常会导致页面错误,而一直不释放又导致内存泄露,并且使系统性能大大下降。频繁地创建和销毁内存资源是很耗时间的,因为创建一个对象要获取内存资源或者其它更多资源。malloc/free 操纵的是进程堆内存,C/C+运行库不允许两个线程同时从内存堆中分配内存,这种多线程同步操作也是
4、相当耗时的。对于共享资源,有一个很著名的设计模式:资源池(Resource Pool)。该模式正是为了解决资源的频繁分配和释放所造成的问题。如何利用已有对象来服务就是一个需要解决的关键问题,其实这就是一些“池化资源”技术产生的原因。数据库连接池、内存池等正是基于这一思想而产生的。对于单核 PC,多线程微观串行;对于多处理器系统,使用多线程技术可以充分发挥硬件的优势。理论上,安装了 N 核 CPU 的 PC,在某一时刻,系统底层所能并发执行的线程个数为 N。然而,线程的数量并不是多多益善。首先,线程这种内核资源的创建和销毁本身就很耗系统资源;其次,频繁的线程上下文切换也会耗费较多的 CPU 时钟
5、周期。借鉴数据库连接池和内存池的池化管理思想,对于线程也可以实行池化管理。在讨论 WinSock 的五种 I/O 模型中,选择模型(select 、WSAAsyncSelcet、WSAEventSelect)基于消息轮询或事件等待,对于多用户并发响应往往为每个客户连接创建一个 I/O 伺服线程。这种单连接单线程的处理方式,对于中小型服务器较为通用,但对大规模多用户的服务器的高并发需求无能为力。完成端口模型本质上利用了 Win32 重叠 I/O 机制,底层利用完成端口队列对象来管理一个线程池。关于线程池规模,根据经验为每个处理器创建 2 个线程,即工作线程数为 CPU 数的两倍,因为并不是每个线
6、程都是可调度的。参考 深度探索 I/O 完成端口、 WinSock 完成端口 I/O 模型。一个大规模高并发的服务器对于资源的管理至关重要,因此往往同时使用数据库连接池、内存池和线程池,对关键资源实行池化管理。一般一个简单线程池至少包含下列组成部分。线程池管理器(ThreadPoolManager):用于创建并管理线程池工作线程(WorkThread):线程池中线程任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行。任务队列:用于存放没有处理的任务。提供一种缓冲机制。基于 IOCP 使用资源池化技术实现高性能的服务器,参阅王艳平、张越著Windows 网络与通信程序设计第 4 章IOCP 与可伸缩网络程序中的CIOCPServer 例程。下图为 CIOCPServer 的系统结构图。CIOCPServer 系统结构图参考:线程池的介绍及简单实现深入研究线程池 一个 C+多线程程序开发库 一个 boost 底下的线程池