第一部分:线程
-
线程其实可以理解为一段正在执行中的代码,它最少由一个线程内核对象和一个栈组成。
-
线程之间是没有从属关系的,同一进程下的所有线程都可以访问进程内的所有内容。
-
主线程其实是创建进程时创建的线程,主线程一旦退出,所有子线程也会退出。
创建一个线程
#include <stdio.h> #include <process.h> #include <windows.h> // 线程函数 DWORD WINAPI WorkerThread(LPVOID lpThreadParameter) { while (true) { printf("WorkerThread() "); } } int main() { DWORD ThreadId = 0; // 如何创建一个线程 HANDLE Thread = CreateThread( NULL, // 安全属性 0, // 设置栈的大小,使用默认 WorkerThread, // 表示的是线程的开始位置 NULL, // 线程函数的参数 NULL, // 创建标志 &ThreadId); // 创建出的线程的 Id // 可以使用 process.h 提供的函数更加安全的创建和结束线程 // _beginthreadex() + _endthreadex() while (true) { printf("main() "); } return 0; }
代码 - 等待线程
#include <stdio.h> #include <windows.h> // 线程函数 DWORD WINAPI WorkerThread(LPVOID lpThreadParameter) { // 获取传入的参数 int count = (int)lpThreadParameter; // 循环次数 100 for (int i = 0; i < count; ++i) printf("i = %d ", i); return 0; } int main() { // 如何创建一个线程 HANDLE Thread = CreateThread( NULL, // 安全属性 0, // 设置栈的大小,使用默认 WorkerThread, // 表示的是线程的开始位置 (LPVOID)500, // 线程函数的参数 NULL, // 创建标志 NULL); // 创建出的线程的 Id // 线程内核对象的信号: // - 有信号: 当线程运行结束的时候,处于有信号状态 // - 无信号: 当线程正在执行的时候,处于无信号状态 // 等待线程知道线程退出为止 WaitForSingleObject(Thread, INFINITE); // 主线程一旦退出,子线程也会退出 return 0; }
代码 - 遍历线程
#include <stdio.h> #include <windows.h> // 1. 包含头文件 #include <TlHelp32.h> int main() { int Pid = 0; scanf_s("%d", &Pid); // 2. 拍摄当前所有线程的快照,参数 2 是无效的,传入任何值遍历的都是所有的线程 HANDLE Snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); // 3. 检查快照是否创建成功 if (Snapshot == INVALID_HANDLE_VALUE) { MessageBox(NULL, L"快照创建失败", L"标题", MB_OK); ExitThread(-1); } // 4. 创建结构体用于保存遍历到的信息 THREADENTRY32 ThreadInfo = { sizeof(THREADENTRY32) }; // 5. 尝试遍历到第一个线程信息 if (Thread32First(Snapshot, &ThreadInfo)) { do { // [ 判断遍历到的线程所属进程 id 是否为想要遍历的进程 ] if (Pid == ThreadInfo.th32OwnerProcessID) { printf("tid: %d ", ThreadInfo.th32ThreadID); } } while (Thread32Next(Snapshot, &ThreadInfo)); } return 0; }
代码 - 挂起和恢复
#include <stdio.h> #include <windows.h> // 1. 包含头文件 #include <TlHelp32.h> int main() { int Pid = 0; scanf_s("%d", &Pid); // 2. 拍摄当前所有线程的快照,参数 2 是无效的,传入任何值遍历的都是所有的线程 HANDLE Snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); // 3. 检查快照是否创建成功 if (Snapshot == INVALID_HANDLE_VALUE) { MessageBox(NULL, L"快照创建失败", L"标题", MB_OK); ExitThread(-1); } // 4. 创建结构体用于保存遍历到的信息 THREADENTRY32 ThreadInfo = { sizeof(THREADENTRY32) }; // 5. 尝试遍历到第一个线程信息 if (Thread32First(Snapshot, &ThreadInfo)) { do { // [ 判断遍历到的线程所属进程 id 是否为想要遍历的进程 ] if (Pid == ThreadInfo.th32OwnerProcessID) { printf("tid: %d ", ThreadInfo.th32ThreadID); // 打开目标线程的句柄 HANDLE Thread = OpenThread(THREAD_ALL_ACCESS, FALSE, ThreadInfo.th32ThreadID); // 尝试进行挂起, 每调用一次就挂起一次 // SuspendThread(Thread); // 尝试进行恢复,每调用一次就恢复一次 // ResumeThread(Thread); // 当挂起计数为 0 的时候,线程就会被调度 // 用于结束标目线程 // TerminateThread(Thread, 0); } } while (Thread32Next(Snapshot, &ThreadInfo)); } return 0; }
代码 - 伪句柄产生的问题
#include <stdio.h> #include <windows.h> // 使用伪句柄作为参数传递可能会带来的问题 // 功能函数,通过传入的线程句柄,获取到线程的创建时间 VOID GetThreadCreateTime(HANDLE Thread) { // 0. 创建用于保存线程相关时间的结构 FILETIME CreateTime = { 0 }, ExitTime = { 0 }; FILETIME KernelTime = { 0 }, UserTime = { 0 }; // 1. 使用 GetThreadTimes 获取到传入的线程的相关时间 GetThreadTimes(Thread, &CreateTime, &ExitTime, &KernelTime, &UserTime); // 2. 将时间转换为本地时间 FILETIME LocalCreateTime = { 0 }; FileTimeToLocalFileTime(&CreateTime, &LocalCreateTime); // 3. 将时间戳转换为系统时间 SYSTEMTIME SystemTime = { 0 }; FileTimeToSystemTime(&LocalCreateTime, &SystemTime); // 4. 输出时间 printf("CreateTime: %d 时 %d 分 %d 秒 ", SystemTime.wHour, SystemTime.wMinute, SystemTime.wSecond); } // 线程函数 DWORD WINAPI WorkerThread(LPVOID lpThreadParameter) { // 接收传入到线程内的伪句柄 HANDLE Thread = (HANDLE)lpThreadParameter; // 根据[伪句柄]输出线程的创建时间 // [输出的实际上是自己的创建时间] GetThreadCreateTime(Thread); return 0; } int main() { // 获取当前线程的[伪]句柄 HANDLE Thread = GetCurrentThread(); // 1. 查看当前[主]线程的创建时间 GetThreadCreateTime(Thread); // 2. 等待 2 秒钟,不能保证 Sleep(2000); // 3. 创建一个新的线程,将伪句柄传入 HANDLE Handle = CreateThread(NULL, 0, WorkerThread, (LPVOID)Thread, NULL, NULL); // 4. 等待线程执行完毕 WaitForSingleObject(Handle, INFINITE); return 0; }
总结:由于传入的句柄是一个伪句柄,始终指向当前的线程内核对象,所以导致在工作线程内计算出的时间不是主线程的运行时间。线程伪句柄的值始终为【-2】,进程伪句柄的值始终为【-1】
代码 - 真实句柄的获取
// 将伪句柄转换成真实的句柄 DuplicateHandle( GetCurrentProcess(), // 从哪里拷贝 GetCurrentThread(), // 要拷贝什么 GetCurrentProcess(), // 拷贝到哪里去 &Thread, // 保存拷贝到的句柄 0, // 安全访问级别 false, // 是否可以被子进程继承 DUPLICATE_SAME_ACCESS); // 转换选项
线程的退出方式
-
主线程函数(mainWinMain)返回,最为友好,会调用析构函数、会清理栈
-
使用ExitThread:不会调用析构函数
-
使用TerminateThread:不会调用析构函数,不会清理栈
-
结束进程:可能来不及保存工作结果
线程的优先级
-
线程只有相对于进程的优先级,随着进程优先级的改变,线程的相对优先级并不会改变
-
通常情况下手动修改优先级并不会对程序的执行产生变化
-