一、线程间数据的一致性
volatile关键字:这个关键字表示这个变量有可能发生改变,每次需要的时候需要到内存中的获取(因为系统可能优化将常用的数据保存到寄存器中)。
1.排他锁定。
二、使用C Run-time Library
早期的C Run-time Library有数个的全局变量和静态变量,对于多线程的程序来说,使用这个库可能造成冲突,因此后来又设计了一个多线程版的C Run-time Library,这样每个线程都有自己的局部变量取代全局变量和静态变量。这样一个版本用于单线程程序,一个版本用于多线程程序。
注:在MFC中必须使用多线程版本的C Run-time Library,否则会造成链接错误。
为了保证多线程情况下的安全,Run-time Library需要做一些薄记功夫,用于保证为每个线程配置一块新内存,作为线程的局部变量用。这样,CreateThread()有一个名为_beginthreadex()函数,用于负责薄记工作。
_beginthreadex()函数和CreateThread函数的参数相同,只是将参数净化了,即WIN32自定义类型转化为C的参数类型。_beginthreadex()函数返回值和CreateThread()相同,但_beginthreadex()函数另外设立了errno和doserrno全局变量,用于_beginthreadex()函数调用失败的原因。而在_beginthreadex()函数中又调用了CreateThread()函数,因此我们还需要CloseHandle()函数来释放线程内核对象。
还有一个用于结束线程的C Run-time Library函数:_endthreadex(); 可以被任意线程的任意时候调用。
注:不要在一个“以_beginthreadex()启动的线程”中调用ExitThread(),因为这样C Run-time Library会没有机会释放为该线程而配置的资源。
下面的情况,你应该使用多线程版的C Run-time Library,并且使用_beginthreadex()和_endthreadex()函数:
A) 在C程序中使用malloc()和free(),或是在C++程序中使用new 和delete.
B) 嗲用stdio.h或io.h中声明的任何函数。
C) 使用浮点变量或浮点运算函数
D) 调用任何一个使用了静态缓冲区的runtime函数,如asctime()、strtok()或rand()。
为了清除C Run-time Library中的结构,对于以_beginthred()或_beginthreadex()来来产生新线程程序,可以通过使用下面两种技术来结束整个程序:
A) 调用C Run-time Library的exit()函数。
B) 从main()返回系统。
为什么应该避免使用_beginthread()函数?
_beginthread函数的原型: unsigned long _beginthread(
void (_cdecl *start_address)(void *),
unsinged stack_size,
void *arglist);
这个函数返回一个线程句柄,但_beginthread返回后第一件事就是关闭自己的handle,因此这个句柄可能是不可用的,因此使用这个句柄会造成错误。并且这个函数和CrateThread函数的参数不一样,有些事情它无法办到,无法让线程产生于挂起状态。
结束线程:_endthread();这个函数无错误代码,无法使用GetExitCodeThread()获取。
三、使用C++
使用C方式的线程函数或者将线程函数设置静态的。如果是非静态的成员函数,其会有一个this指针,这样会造成this指针丢掉,造成错误。
四、MFC中的线程
如果要在MFC程序中产生一个线程,而该线程将调用MFC函数或使用MFC的任何数据,那么你必须以AfcBeginThread()或CWinThread::CreateThread()来产生这些线程。
五、GDI和窗口管理
在win32中维护着一个系统消息队列,对于每个GUI线程都有自己的专属消息队列,并不意味着每个窗口都有自己的消息队列。对于非GUI线程来说,建立起初没有消息队列,当第一次调用GDI函数时,系统才会为线程创建消息队列。
所有传送给某一窗口的消息,都将由产生该窗口的线程负责处理。
使用SendMessage函数将消息传给另一个线程所要做的工作:系统必须做一次上下文切换(context switch),切换到另一个线程,调用窗口函数,然后再做一次上下文切换,这个代价是比较大的,因此应该尽量将窗口保存在一个线程中。同一线程中将减少上下文切换过程。
sendmessage正在被调用时,还能够处理由sendmessage产生的消息,但不能处理其他消息。当执行线程(接收SendMessage传过来消息的线程)调用一下函数时,等待的线程(调用Sendmessage的线程)将必须醒来:
A) DialogBox()
B) DialogBoxIndirect()
C) DialogBoxIndirectParam()
D) DialgBoxParam()
E) GetMessage()
F) MessageBox()
G) PeekMessage()
我们可以通过调用ReplayMessage()函数来让等待的消除能够继续工作。
为了解决调用SendMessage函数造成等待线程一直等待的问题,有两个函数:
A) SendMessageTimeout(); 允许你指定一个时间,时间终了后不管对方怎样,SendMessageTimeout一定返回。对方挂了,也会自动返回。
B) SendMessageCallback(); 该函数会理科返回,但其参数之一时一个函数地址,应该被以SendMessage()的方式调用起来。
线程间通信:
A)PostThreadMessage();这个函数多了一个线程ID,这个函数将线程传送给另一个线程,当然另一个线程必须要有消息队列。
B)全局变量
六、进程间通讯
A) WM_COPYDATA消息:可以将一个线程中的数据搬到另一个线程,不管两个线程是否在同一个进程中。
使用方法: SendMessage(hwnd,WM_COPYDATA,(WPARAM)hwndSender,(LPARAM)&cds);其中cds必须是指向一个特定windows数据结构:
typedef struct tagCOPYDATASTRUCT{
DWORD dwData; //一个用户自定义值,用于区分消息的类型。
DWORD cbData; //lpData数据大小
PVOID lpData; //一块数据,可以被传到接收端
}COPYDATASTRUCT,*PCOPYDATASTRUCT;
你只能使用SendMessage函数发送消息,因为你需要为传送的数据的缓冲区留时间进行处理。
这种方式的缺点:效率不够到,没办法使用PostThreadMessage函数。
B) 共享内存 (文件映射) 函数:CreateFileMapping,OpentFileMapping,MapViewOfFile,UnmapViewOfFile,CloseHandle.
C) Anonymous Pipes
D) Named Pipes
E) Mailslots
F) OLE Automation
G) DDE