读后感:
这本书是一本Win32下多线程的抗鼎之作,其涵盖了多线程编程的方方面面。我阅读的原因在于期望能了解原汁原味的Win32下多线程编程。而我以前只是使用BCB的TThread等对象进行Win32多线程快速开发。阅读后的确对于Win32下的许多概念清楚明了不少,但也产生许多新的疑问。但我知道,这些疑问许多并不能在这本书中获得求解。在.Net下的Windows多线程开发已经有了新的发展,需要我继续努力学习。
我以前对侯捷老师的译注总有些不适应,总感觉侯老师严谨有余,亲和力不足。但这本书中,侯老师的精彩译注,以及对于原著错误的指正,使我的阅读受益匪浅。我才真正感受到严谨的魅力。感谢侯老师。
下面是我的笔录,其中{}为我的随感。
笔录:
P4
多线程,使程序得以将其工作分开,独立运作,不互相影响
P12
线程廉价:线程启动快,退出快,对系统资源冲击小,且线程彼此分享了大部分核心对象的拥有权。
P26
WinBASE.H -->CreateThread 产生一个线程
P33
1.多线程程序无法预期-->掌握多线程程序的预期是一项很高级的分析编程能力
2.执行次序无法保证
3.线程对于小的改变有高度的敏感-->调试器会引入不确定
4.线程并不是立刻启动
5.线程上的资源泄露更常见且更致命的
CloseHandle释放核心对象;GetExitCodeThread 线程结束代码;
ExitThread 强制结束线程
P47
主线程:Primary Thread
1.负责 GUI(Graphic User Interface)
2.该线程结束会使其它线程强迫结束、无从清理;
P48
经验显示,线程的各种相关函数是错误高危险群,而适当的错误处理可以阻止挫败并产生一个比较可信赖的程序;
P51
Microsoft Threading Model --> GUI Thread、Worker Thread
P53
设计目标:简单和安全、更甚于复杂和速度;
在线程之间以最低表面积来设计程序;
[最低表面积:线程共享的数据结构]
P59
成功秘诀:
1.各线程的数据要分离开来,避免使用全局变量;
2.不要在线程之间共享GDI对象;
3.确定你知道你的线程状态,不要径自结束程序而不等待它们的结束;
4.让主线程处理用户界面(GUI)
P62
绝不要在Win32中使用BusyLoop;
[应该用WaitForMultipleObjects\MsgWaitForMultipleObjects]
[控制自由触发Event为非自由触发Event,这样可以来避免系统的死锁]
P75
Unix的Signal-->立即的消息信号,有冲击
Windows Signaled Object -->内部状态的改变,无冲击
P94
在Windows系统中:PostMessage() 异步 并行;SendMessage() 同步 串行
P95
最容易的同步机制:Critical Sections
InitializeCritical Section \ DeleteCriticalSection \ EnterCriticalSection \ LeaveCriticalSection
[EnterCriticalSection 可嵌套]
[加上CriticalSection保护后可以实现数据操作的"原子化"]
P100
不要长时间锁住一份资源;而CriticalSection是不知道线程的生死的-->Mutex(互斥器)-->WaitForMultipleObjects
P108
Mutex-->核心对象,牺牲存储保证运行稳定;可跨线程\跨进程使用;Mutex有可能被"不负责"的线程"遗弃"。
[Mutex是Windows所特有,在其它系统中则使用Semaphore{旗语}
P120
EventObject(事件)-->最具弹性的通步机制;Event的状态完全在你的控制之下;可以精确地告诉一个Event对象做什么事,以及什么时候去作;其应用于多种类型的高级IO操作中。
CreateEvent\SetEvent\ResetEvent\PulseEvent
P128
同步机制摘要*
P132
TerminateThread -->放弃一个线程-->最糟糕的想法,永远不要这么用。
P135
Windows中终结Thread的标准操作方式:建立一个Event对象来自检Thread消亡,即周期性地检查一个Event对象,以决定要不要结束自己。
P140
优先级:线程的社会阶层
P157
Overlapped IO是一个高层技术:Windows给于我们具有Thread能力的IO函数
P198
要加上Volatile量来表明其在多线程下不能被编译器错误优化。
{对单线程有效优化对于多线程常带来BUG}
P200
{引入数据库的Transaction(事务)来解决多线程的数据一致性问题}
{侯捷先生的译著中这样的拓展译注很有用,且尊重出处,当效之}
P204
{在不考虑效率下,将锁定范围扩大会解决很多棘手问题。如将一个树型数据集根点锁定以避免其它线程的非法操作}-->排他锁定
P215
{作者给出一个Read/Write锁定方案,没看懂}
Fine Granularity{逐个锁}:很容易产生死锁、效率瓶颈的情况尚可令人放心、"锁定"所花费的时间可能招致高度浪费。 Coarse Granularity{全部锁}:使用简单、死锁风险极低、容易产生效率瓶颈。两者都是十分极端的。
P260
一个非静态的类成员函数都有一个隐藏起来的参数被推入堆栈中,当编译器需要处理类的成员变量时,它需要这个隐藏参数的帮助,这就是"this"参数(一个指针)
P261
为了以一个成员函数启动一个线程,要么你就使用静态成员函数,要么你就得使用C函数(而非C++成员函数)
P274
异常情况处理机制的好处在于,它在标准函数调用和返回(Call/Return)模式之外运作。
P277
如果要在MFC程序中产生一个线程,而该线程将调用MFC函数或使用MFC的任何数据,那么你必须以AfxBeginThread或CWinThread::CreateThread来产生这些代码
P278
面对Windows程序设计中的线程,有下列三种层次:
1.Win32API CreateThread() EndThread()
2.C Runtime Library _beginthreadex() _endthreadex()
3.MFC AfxBeginThread() {MFC方式封装了上述两种方法}
P286
{MFC给我们作了许多封装,在该页给出一个NakedVersion来说明封装细节}
P294
你不能放一个指针(指向一个CWnd)到结构中。而该结构被一个Worker线程使用。你也不能把一个指向CDialog或CView的指针交给另一个线程
{这是享受MFC方便同时的代价}
P300
虽然MFC的同步控制类对于基本结构的锁定很有帮助,但它们面对比较复杂的工作时却黯然失色。{这是享受MFC方便同时的代价}
P304
所有传送给某一个窗口之消息,将由产生该窗口之线程负责处理。{这是一条设计原则,这样可以使UI设计更清晰}
P311
欲在一个MDI程序中有效率地使用多个线程,我的建议是以一个线程处理所有的用户输入,以及所有用户界面的管理,然后使用一个以上的线程来负责诸如重绘、打印等工作。......你的主线程(负责主框架窗口)总是应该能够有所回应,不会陷入长时间计算的泥沼中。
P318
所有这些问题或许都可以绕道解决。但很明显我正在强迫系统做一些它并没有被设计那么做的事情。我越是想办法挖东墙补西墙,整个机制就越是变得脆而虚弱。......这个例子提醒我不适当使用线程所带来的危险。不幸的是,在"适当使用"与"不适当使用"之间,界限模糊。
{清楚了解系统内部机制是很重要的。不要用蛮力,要从内部挖掘,攻"心"为上}
P321
对于一个多线程程序调试,如果没有经过小心的策划,很可能就像抓蝴蝶一样。你可以看到问题,接近它,但是当你几乎已经就要用手抓着它时,它飞走了,然后在另一个地方出现。
P325
运转记录:程序全身插满printf();{要注意I/O是很慢的操作,这样就会对程序本身有冲击}
P336
进程可用来作为"隔板"机制。这有点像船舱被分割为防水壁和许多船壳一样。利用不同的进程所产生出来的边界,可以阻止错误到处蔓延。
P336
在同一进程的不同线程之间搬移数据,最简单的一种做法就是利用消息队列。......Windows定义了一个消息,名为 WM_COPYDATA,专门用来在线程之间搬移数据--不管两个线程是否同属一个进程,和其它所有的消息一样,你必须指定一个窗口,也就是一个 HWnd,当作消息的目的地。所以接受此消息的线程必须有一个窗口(译注:也就是它必须是个UI线程)
P339
进入C++世界,情况又更复杂些了,绝不可以把"指向某个拥有虚函数的对象"的指针当作lpData来传递{这样就很麻烦了,因为在MFC中,由于继承,多数对象都具有virtual的析构函数,如CString。这样就无法直接使用它们作为WM_COPYDATA的传递参数来在进程间传递数据。如此看来WM_COPYDATA局限性很大,是"戴着脚镣跳舞"呀}
P344
WM_COPYDATA是唯一一个可以在16位程序和32位程序之间搬移数据的方法;但其效率低,不能传递给没有窗体的线程,不支持PostThreadMessage
P345
Win32的一个基础观念就是,进程之间要有严密的保护。每一个进程认为它拥有整部机器,从一个进程中要看到另一个进程的地址空间的任何一部分,都是不可能的。
P346
所谓共享内存,是一块在设计时即打算给一个以上的进程在同一时间都看到的内存区域。
P346
设定一个共享内存区域(Shared Memory Area)需要两个步骤:
(1)产生一个所谓的file-mapping核心对象,并指定共享区域的大小-->CreateFileMapping
(2)将共享区域映射到你的进程的地址空间中。-->MapViewofFile/UnmapViewofFile-->OpenFileMapping/CloseHandle
P354
任何Collection Class(译注:用于array、list、map之C++类),不论来自MFC或其它地方,都不能够安全地使用于共享内存中。
P361
使用共享内存不是件容易的事{对于多线程,也是一样}
0 评论:
发表评论