线程内核对象
每个进程里,都有一个线程,操作系统用线程内核对象来管理线程。
线程有2部分组成:1 线程内核对象。 操作系统来管理线程 2 线程栈, 来存在函数和局部变量的地址空间。
什么情况下使用多线程?
当各个任务彼此独立时,可以使用多线程,如:开一个后台线程来检测语法。
什么情况下不要使用多线程?
任务联系紧密。如 扫描一个目录的文件,就不适合开多个线程处理,这样处理起来复杂度很高!
实现CreateThread 来创建一个线程.
如果是C、C++代码的话,使用_beginThreadEx来创建线程
为什么? 因为有些C函数里面使用了静态变量,那么多个线程执行的话,会有冲突,但是_beginThreadex做了线程局部处理,它内部就调用了CreateThread函数。
WINBASEAPI 表示是winbase.h的API
__out_opt 表示是输出
HANDLE
WINAPI
CreateThread(
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, 安全属性,一般为NULL表示默认
__in SIZE_T dwStackSize, 线程栈的空间,一般使用0来表示默认值
__in LPTHREAD_START_ROUTINE lpStartAddress, 线程函数地址
__in_opt LPVOID lpParameter, 线程函数参数
__in DWORD dwCreationFlags, 创建线程时,指定啥标志 0 表示立即开始执行,CREATE_SUSPENDED 表示暂停
__out_opt LPDWORD lpThreadId 返回线程ID,不需要时,可以传递NULL,表示不关心
);
__out_opt 表示是输出
HANDLE
WINAPI
CreateThread(
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, 安全属性,一般为NULL表示默认
__in SIZE_T dwStackSize, 线程栈的空间,一般使用0来表示默认值
__in LPTHREAD_START_ROUTINE lpStartAddress, 线程函数地址
__in_opt LPVOID lpParameter, 线程函数参数
__in DWORD dwCreationFlags, 创建线程时,指定啥标志 0 表示立即开始执行,CREATE_SUSPENDED 表示暂停
__out_opt LPDWORD lpThreadId 返回线程ID,不需要时,可以传递NULL,表示不关心
);
终止运行线程的4种方式:
1 线程函数返回(
推荐)
可以清理C++对象资源,会调用C++的析构函数
清理线程栈
2 自己调用ExitThread(
避免使用)
不会调用C++的析构函数
清理线程栈
如果使用的是C++代码,那么调用_endThreadEx() 来替换ExitThread
3 其它线程调用TerminateThread()
避免使用
不会调用C++析构函数,也不会清理线程栈
ExitThread来结束的线程,会清理堆栈,但是调用terminateThread()函数 不会清理堆栈信息,除非线程所在进程终止。
4 线程所在的进程终止
避免使用
C++析构函数不会调用,线程被强行杀死,正常的清理工作没有执行。
线程终止时,会发生以下事情:
1 线程拥有的所有对象句柄被释放
一个线程有2个用户对象: 窗口和挂钩
2 退出码有STILL_ACTIVE 变成 ExitThread 或者TerminateThread 返回的退出码
3 线程内核对象变成触发状态
4 如果线程是最后一个活动线程,那么进程也终止。
5 线程内核对象的引用计数减一
C和C++库
标准的C和C++库,最开始不是为了多线程设计的,在多线程环境下有些函数会出问题:
errno,_doserrno, strtok, _wcstok, strerror, _strerror, tmpnam, tmpfile,asctime,gmtime,等
所以要不要调用系统的CreateThread,而要调用C,C++运行库 _beginthreadex
_beginthreadex 分配和初始化 初始化数据块,并将它与新的线程关联起来
_endthreadex 函数 在线程终止运行的 释放分配的数据块
不要使用C和C++运行库的这一对函数
_beginthread ,
_endthread
了解自己的身份
返回当前进程的句柄 和 ID
HANDLE GetCurrentProcess();
DWORD GetCurentProceddID();
返回当前线程的句柄 和 ID
HANDLE GetCurrentThread();
DWORD GetCureentThreadID();
注意: 上面这2个函数返回的都是
伪句柄
伪句柄:表示当前线程的句柄,即调用线程的句柄。下面举一个示例来分析
#include <iostream>
#include <vector>
#include <map>
#include <Windows.h>
#include <tchar.h>
#include <process.h>
#include <Strsafe.h>
HANDLE ghthreadCurrent = NULL;
unsigned int WINAPI ThreadFunc2(LPVOID lp);
unsigned int WINAPI ThreadFunc1(LPVOID lp)
{
HANDLE hThread = GetCurrentThread();
ghthreadCurrent =(HANDLE) _beginthreadex(NULL, 0, ThreadFunc2, hThread, 0, NULL);
while(1)
{
printf("111\n");
Sleep(1000);
}
return 0;
}
unsigned int WINAPI ThreadFunc2(LPVOID lp)
{
HANDLE hThread = (HANDLE)lp;
TerminateThread(hThread,0);
return 0;
}
int main()
{
HANDLE hThread1 =(HANDLE) _beginthreadex(NULL, 0, ThreadFunc1, NULL, 0, NULL );
WaitForSingleObject(hThread1,INFINITE);
return 0;
}
线程2 的线程函数 TerminateThread 会把自己杀死,不会杀死线程1
使用一个复制句柄的函数 DuplicateHandle() 来复制句柄
#include <iostream>
#include <vector>
#include <map>
#include <Windows.h>
#include <tchar.h>
#include <process.h>
#include <Strsafe.h>
unsigned int WINAPI ThreadFunc2(LPVOID lp);
unsigned int WINAPI ThreadFunc1(LPVOID lp)
{
HANDLE hThread = GetCurrentThread();
HANDLE hTagrealThread = NULL;
BOOL bOk = DuplicateHandle(GetCurrentProcess(),hThread,GetCurrentProcess(),
&hTagrealThread,0,FALSE,DUPLICATE_SAME_ACCESS);
(HANDLE) _beginthreadex(NULL, 0, ThreadFunc2, hTagrealThread, 0, NULL);
while(1)
{
printf("111\n");
Sleep(1000);
}
return 0;
}
unsigned int WINAPI ThreadFunc2(LPVOID lp)
{
HANDLE hThread = (HANDLE)lp;
Sleep(5000);
TerminateThread(hThread,0);
//必须要关闭句柄
CloseHandle(hThread);
return 0;
}
int main()
{
HANDLE hThread1 =(HANDLE) _beginthreadex(NULL, 0, ThreadFunc1, NULL, 0, NULL );
WaitForSingleObject(hThread1,INFINITE);
CloseHandle(hThread1);
printf("main exit\n");
system("pause");
return 0;
}
线程2函数,杀死的是线程1
最后要关闭线程1的线程内核对象
复制一个句柄后,线程内核对象引用计数会加1