Windows 线程
线程函数
我们的main/WinMain函数其实就是我们的线程入口函数.
线程堆栈
使用CreateThreadAPI可以创建一个线程.
它也会创建一个线程内核对象
- 但它是一个结构体, 它并不表示当先线程的本身.
- 它主要用于操作系统对我们线程的管理.
它也会分配一块当前线程的堆栈
- 线程和线程之间的堆栈内存空间也是独立分开的
- 但是它们的堆栈空间是在同一个进程的内存中
CreateThread API
HANDLE WINAPI CreateThread(
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程安全结构体, 一般设置为NULL
_In_ SIZE_T dwStackSize, // 堆栈大小, 可为0, 由编译器决定, 单位是BYTE
_In_ LPTHREAD_START_ROUTINE lpStartAddress, // 线程开始地址(入口函数)
_In_opt_ LPVOID lpParameter, // 线程的参数
_In_ DWORD dwCreationFlags, // 运行方式
_Out_opt_ LPDWORD lpThreadId // 线程的标识符, 输出参数, 可为NULL
);如果想要子进程可以继承该线程句柄, 则需要有一个
SECURITY_ATTRIBUTES结构体typedef struct _SECURITY_ATTRIBUTES { DWORD nLength; LPVOID lpSecurityDescriptor; BOOL bInheritHandle; } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;- 并将结构体中的
bInheritHandle设置为TRUE
- 并将结构体中的
堆栈大小的设置
- 通过设置
dwStackSize参数来设置线程的堆栈大小 可以设置为0
- 这样就由
编译器来决定堆栈的大小 - 在VisualStudio中可以进行设置, 默认是 1MB
- 这样就由
当线程将自己所拥有的堆栈空间不够用时, 会抛出异常(堆栈溢出)
- 这个异常会被捕获, 继而处理这种异常, 在重新自动重新分配新的栈
线程入口函数
通过设置
lpStartAddress来设置线程的开始地址- 它就是线程的入口函数
- 它的本质是
回调函数 - 函数原型如下
DWORD WINAPI ThreadProc( _In_ LPVOID lpParameter );
线程参数
- 通过设置
lpParameter来给线程传递参数 - 它会由
ThreadProc的lpParameter进行接收 - 一般我们会传递一个
对象的地址来达到线程间的数据共享 - 示例
#include <Windows.h>
DWORD WINAPI ThreadMain(LPVOID lParam)
{
int nParam = (INT)lParam;
return 0; // 线程退出代码
}
int main()
{
HANDLE hThread = CreateThread(NULL, 0, ThreadMain, (LPVOID)0xFFFF, 0, NULL);
CloseHandle(hThread);
return 0;
}线程运行方式
通过
dwCreationFlags来设置线程的运行方式0
- 线程立即运行
CREATE_SUSPENDED
- 暂停状态, 线程不会运行, 除非调用
ResumeThread方法
- 暂停状态, 线程不会运行, 除非调用
STACK_SIZE_PARAM_IS_A_RESERVATION
- 表示
dwStackSize参数指定的是堆栈的初始保留大小 - 不指定此标志,
dwStackSize将指定堆栈提交大小
- 表示
线程标识
- 可以通过
lpThreadId来接收线程ID - 可以传递NULL不进行接收
主线程退出和其他线程退出的区别
主线程启动一个子线程
- 主线程消亡后, 子线程也会消亡
- 因为主线程消亡后, 表示我们的进程消亡了
- 参见例子1
子线程A启动一个子线程B
- 子线程A消亡了, 但是主线程还在
- 子线程B依然存在并执行
- 线程之间会以抢占的方式进行执行
- 参见例子2
// 例子1
// 主线程启动子线程, 主线程消亡, 子线程也消亡
#include <Windows.h>
#include <tchar.h>
DWORD WINAPI ThreadMain(LPVOID lParam)
{
int nParam = (INT)lParam;
Sleep(100);
_tprintf(TEXT("nParam:[%d]\n"), nParam);
return 0; // 线程退出代码
}
int main()
{
HANDLE hThread = CreateThread(NULL, 0, ThreadMain, (LPVOID)0xFFFF, 0, NULL);
CloseHandle(hThread);
// Sleep(200); // 取消或注释这一句查看运行效果
return 0;
}// 例子2
// 线程抢占执行
// 子线程A启动子线程B的消亡问题
#include <Windows.h>
#include <tchar.h>
DWORD WINAPI ThreadSubMain(LPVOID lParam)
{
int nParam = (INT)lParam;
_tprintf(TEXT("nParam:[%d]\n"), nParam);
return 0; // 线程退出代码
}
DWORD WINAPI ThreadMain(LPVOID lParam)
{
int nParam = (INT)lParam;
HANDLE hThread = CreateThread(NULL, 0, ThreadSubMain, (LPVOID)0x64, 0, NULL);
CloseHandle(hThread);
Sleep(1); // 关键在这一句
// 这一句被注释掉, 会先输出-1, 后输出100, 不注释, 就会先输出100, 后输出-1
// 这是因为, Sleep会导致线程休眠, 让出CPU的时间片, 由第二个线程进行抢占执行
_tprintf(TEXT("nParam:[%d]\n"), nParam);
return 0; // 线程退出代码
}
int main()
{
HANDLE hThread = CreateThread(NULL, 0, ThreadMain, (LPVOID)0xFFFFFFFF, 0, NULL);
CloseHandle(hThread);
while (TRUE)
{
Sleep(100);
}
return 0;
}线程间参数传递
在上面的例子中, 我们简单的使用了一下线程的参数, 只传递了数字.
在我们的实际应用中, 我们对线程传递的参数必须保证参数的
有效性- 可以将参数放到
堆上, 也就是new出来 - 可以通过
静态全局变量来达到数据的同步
- 可以将参数放到
- 线程的执行是并行的
- 线程的执行顺序是不确定的
#include <Windows.h>
#include <tchar.h>
class CThreadParam
{
public:
INT m_nNum;
};
DWORD WINAPI ThreadOtherMain(LPVOID lParam)
{
Sleep(100); // 模拟让ThreadMain运行完成
CThreadParam* demo = (CThreadParam*)lParam;
_tprintf(TEXT("m_nNum:[%d]\n"), demo->m_nNum);
return 0;
}
DWORD WINAPI ThreadMain(LPVOID lParam)
{
_tprintf(TEXT("In ThreadMain....\n"));
Sleep(100);
CThreadParam demo;
demo.m_nNum = 100;
// 这时候的传值是不正确的, 因为没有保证 demo 的有效性
CloseHandle(CreateThread(NULL, 0, ThreadOtherMain, &demo, 0, NULL));
_tprintf(TEXT("Finish ThreadMain....\n"));
return 0;
}
INT main()
{
CThreadParam param;
// 这里这样传参是没问题的, 因为我们进行了等待, 一定可以保证 param 的有效性
HANDLE hThread = CreateThread(NULL, 0, ThreadMain, ¶m, 0, NULL);
_tprintf(TEXT("Will Exec WaitForSingleObject....\n"));
// 有可能先出现Will... , 也可能先出现 In ThreadMain. 不一定先执行谁
// 可以利用 Sleep(1) 切换线程状态, 控制线程执行顺序
WaitForSingleObject(hThread, INFINITE); // 等待线程完成
CloseHandle(hThread); // 关闭线程句柄
return 0;
}未完待续...
如有错误,请提出指正!谢谢.
本文由 花心胡萝卜 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: 2017-06-13 at 02:24 pm