最近学习了多线程编程的一些东西。先要感觉“万一”,这个是他的Blog地址:http://www.cnblogs.com/del/category/174761.html
学完感觉概念已经完全清楚,再回头看以前的疑问觉得应该帮后来的学习者澄清几个概念以方便学习:
基础:
一、线程同步(Thread Synchronize)这个翻译是有问题的,应该是译为:线程协调。因为我们的学习和使用的目的都是为避免线程同步带来的冲突。
二、临界区因是进程内的对象,所以使用它的成本最低。而其它的几个都是系统内核级别的对象,使用的开销要大于临界区。
三、我把受临界区,互斥量,信号量和事件控制段的代码和变量简称为:协调代码。下面将会多次提到。
关键理解:
一、临界区和互斥量可是视为相同的类型,区别是临界区只能用于进程内,而互斥量可用于不同进程中不不同线程。
对这两个对象理解的关键是:线程串行化。或是通俗的说为:对临界区和互斥量段内协调代码的执行,要求所有线程排队通过。相对高并发的多线程和协调代码执行时间较长的情况来说,效率最低。
为什么效率低是因为系统为线程分配CPU时间片时,如果此线程要求进入临界区或互斥量但已有线程在使用,那么它将被挂起。而下次此线程分到时间片时,首先要查询临界区或互斥量是否已经空闲。也就是说临界区和互斥量将导致额外的线程挂起和解挂操作。
所以在上述情况下,互斥量和临界区中的代码一定要优化到最小的执行时间(但也不一定是最优的,原因看最后)。
二、信号量的关键理解是:线程并行化。通俗的说法是:一组相同资源被线程并行使用的一种方法,它最大的优点是避免了线程在达到信号量最大容量前的线程调度成本,最典型的例子就是数据库连接池。象形的比喻的是高速公路的出入路口。
三、事件:这个很容易理解:“哥定义了规则,你们(线程)得按这个来。!”
误区:
我在学习时的一个错误理解(这个理解导致我对这些协调方法一直抱有极大的疑问并误导了我):相对CPU最小时间片来说是没有并行这一概念的,所以使用任何一种线程协调方法没有区别。
例如一段协调代码需要1秒完成。同时有10个线程访问这段协调代码,那么无论使用哪一种协调方法。都要花10秒才能完成。所以这些协调方法没有任何意义!
事实是Windows在进行线程调度时,也要花费时间,而上面的说法完全忽略了这个问题。如果在Windows为线程分配时间片时,线程不能完成需要协调的代码。那么在串行方式下带来的线程调度成本将超过并行化的方式(除非这些要求串行的代码可以在一次在Windows分配给线程的CPU时间片上完成需要协调的代码)。
如何使用
我们应该用什么样的方式来使用这些线程协调方法:
一、并行方式的信号量在访问相同的一组资源时是最好的方法,因为它最大限度减少了系统调度线程的成本。
二、临界区和互斥量只应用于访问串行资源(例如使用全局计数器,系统参数访问和修改)。同一进程下的线程串行化时,只应该使用临界区。
三、按指定的规则进行线程协调时使用事件。
真正的难点
实际中使用,个人感觉难的地方并不是如何使用临界区,互斥量,信号量和事件这些代码。而是:
一、考虑到异常的存在,如何写出合理的异常控制代码。
例如ADOConnection在:一、网络断开恢复后;二、SQLServe重新启动后、三、数据库死锁超时后。都需要我们能捕获到异常并通知ConnectionPool对象重置哪个ADOConnection对象(Close;Open)。而这个要求对我们以前已经写好程序来说,带来变化和修改成本可能远远大于写出一个数据库连接池的代码。
二、相同的代码出现在很多地方导致代码无意义的膨胀。以单个线程从数据库连接池中取得一个连接对象再进行数据库操作为例。每次对数据库的操作,至少都需要加上下面的代码(...表示现有代码):
var
ADOCnn: TADOConnection;
begin
ADOCnn := ConnectionPool.Get();
try
ADODataSet.Connection := ADOCnn;
...
finally
ConnectionPool.Return(ADOCnn);
end;
end.
这种变化给我的感觉是需要在设计时更多考虑每个业务对象粒度最小化和输出函数标准化,这样协调对象就可以使用一个标准来调用所有的业务对象。否则这种代码的膨胀将让人很无奈。
注:协调对象如果可以只使用一个标准来调用所有的业务对象,也就是函数标准化。那么上面的代码可以写在协调对象中,...换为进入业务对象函数的代码,将显著减少代码的无意义膨胀。如果函数不能标准化那么也可以说为如何定义出一种简单有效的AppSvr中资源定位方法,并将参数传递给指定业务对象的函数。