多线程多任务学习笔记(二)

来源:互联网 发布:淘宝企业店铺怎么避税 编辑:程序博客网 时间:2024/05/18 19:22

1. 产生一个线程

winbase.h

WINBASEAPI

HANDLE

WINAPI

CreateThread(

    LPSECURITY_ATTRIBUTES lpThreadAttributes,

    DWORD dwStackSize,

    LPTHREAD_START_ROUTINE lpStartAddress,

    LPVOID lpParameter,

    DWORD dwCreationFlags,

    LPDWORD lpThreadId

);

 

注意线程一旦启动(异步asynchronous执行),他就独立于原始调用了。

线程函数的约定:返回值是DWORD,调用约定是WINAPI,有一个LPVOID的参数。

#define WINAPI __stdcall windef.h

注意有cpascalstdcall

 

多线程的调用无法预期

执行次序无法保证,Task Switch可能在任何时刻任何地点发生

线程并不是立刻启动

例子:

#define WIN32_LEAN_ANDMEAN

#include <stdio.h>

#include <stdlib.h>

#include <windows.h>

 

DWORD WINAPI ThreadFunc(LPVOID)

 

int main()

{

HANDLE hThread’

DWORD theadID;

int i;

for(i = 0; i<5; i++)

{

hThread = CreateThread(NULL,

0,

ThreadFunc,

(LPVOID)i,

0,

&threadID);

if(hThread)

{

printf(“Thread launched %d/n”,i);

}

sleep(2000);

return EXIT_SUCCESS;

}

 

DWORD WINAPI ThreadFunc(LPVOID n)

{

int i;

for (I = 0; i<10;i++)

{

printf(“%d%d%d%d%d%d%d%d/n”,n,n,n,n,n,n,n,n);

}

return 0;

}

 

可能会有

33344444444

….

33333

….

发生。

像这样的问题是可以解决的,只要在编译时使用/MT/MD选项,表示要使用多线程版本的C runtime library

 

2. 核心对象kernel object

CreateThread()传回两个值,用于识别一个新的线程:HANDLElpThreadId带来的线程ID。线程ID是个全局变量,可以独一无二的表示系统任一进程中的某个线程。AttachThreadInput()和PostThreadMessage()就可需要用到线程ID,这两个函数允许你影响其他人(线程)的消息队列。调试器和进程观察器也需要线程ID。为了防护的缘故,不可以更具一个线程ID而获得其handle

CreateThread()返回的handle即是一个核心对象(kernel object),他有Kernel32.dll管理。所谓handle,其实是个指针,直线操作系统内存空间中的某样东西,它被操作系统隐藏了细节,不允许被直接获得,为的就是维护系统的安全性。

Win32核心对象有:

进程processes

线程threads

文件files

事件events

信号量semaphores

管道pipes

 

核心对象可以有一个以上的拥有者,甚至可以跨进程,核心对象保持了一个引用计数(reference count),以记录有多少个handles对应到此对象,对象中也记录了哪一个进程或线程是拥有者。如调用CreateThread()或其他会传回handle的函数,引用计数会加1 当调用CloseHandle()时,引用计数会减1。一旦引用计数降至0,这一核心对象即自动销毁。

由于引用计数的设计,对象有可能在“产生该对象之进程”结束之后还继续幸存。Win32提供各种机制,让其他进程得以取得一个核心对象的handle,如果某个进程握有某个核心对象的安顿了,而该对象的原创者(进程)已经“作古”了,此核心对象并不会被销毁。

 

CloseHandle()的重要性

 

BOOL CloseHandle( HANDLE hObject);

 

如果一个进程在结束之前没有针对其核心对象调用CloseHandle(),操作系统会自动把那些对象的引用计数减1。但是如果一个进程常常产生“Worker线程”(纯粹做运算的线程)而老是不关闭线程的handle,那么这个进程可能最终有数百甚至数千个开启的“线程核心对象”留给操作系统去清理。这样的资源泄漏(resource leaks)可能会对效率带来负面的影响。

 

线程对象和线程是不同的,因此可以在不结束线程的情况下关闭其handle

线程handle是指向“线程核心对象”,而不是指向线程本身。调用CloseHandle()只是说明与此核心对象无任何瓜葛。

 

“线程核心对象”引用的那个线程也会令核心对象开启。因此线程对象的的默认引用计数是2。当调用CloseHandle()时,引用计数减1, 当线程结束时再减1,只有当两件事情都发生了,不管顺序如何,这个对象才会被真正清除。

3.线程结束 Exit Code

BOOL GetExitCodeThread( HANDLE hThread, LPDWORD lpExitCode);

可以获得线程结束代码。

 

4.结束线程

VOID ExitThread( DWORD dwExitCode);

强制结束线程,并制定线程之结束代码dwExitCode

 

 

结束主线程

程序启动后就执行的线程成为primary thread。他有两个特点:第一,它必须负责GUI程序中的主消息循环。第二,该线程结束会使得程序中所有线程都被强迫结束,程序也因此而结束。其他线程没有机会做清理工作。

因此需要注意,在main()WinMain()结束(返回,return)之前,总是先等待所有的线程都结束。(WaitForSingleObject等的应用)

 

5.错误处理

MTVERIFY宏处理

参考Win32多线程程序设计。

 

 

The Microsoft Threading Model微软多线程模型

 

Win32种,线程分为GUI线程和worker线程两种,GUI线程负责建造窗口以及处理主消息循环。Worker负责执行纯粹运算工作。

一般而言,主线程绝对不会做哪些不能够马上完成的工作。

 

GUI线程:拥有消息队列的线程。Worker线程不能够产生窗口。

 

多线程设计成功的关键:

1.  各线程的数据要分离开来,避免使用全局变量,因为容易被改写。

2.  不要再线程之间共享GDI对象。

3.  确定知道线程的状态,不要直接结束程序而不等待他们的结束。

4.  让主线程处理用户界面UI