Windows提高_2.1第一部分:线程
2024-08-30 21:01:10
第一部分:线程
什么是线程?
线程其实可以理解为一段正在执行中的代码,它最少由一个线程内核对象和一个栈组成。
线程之间是没有从属关系的,同一进程下的所有线程都可以访问进程内的所有内容。
主线程其实是创建进程时创建的线程,主线程一旦退出,所有子线程也会退出。
创建一个线程
#include <stdio.h>
#include <process.h>
#include <windows.h>
// 线程函数
DWORD WINAPI WorkerThread(LPVOID lpThreadParameter)
{
while (true)
{
printf("WorkerThread()\n");
}
}
int main()
{
DWORD ThreadId = ; // 如何创建一个线程
HANDLE Thread = CreateThread(
NULL, // 安全属性
, // 设置栈的大小,使用默认
WorkerThread, // 表示的是线程的开始位置
NULL, // 线程函数的参数
NULL, // 创建标志
&ThreadId); // 创建出的线程的 Id
// 可以使用 process.h 提供的函数更加安全的创建和结束线程
// _beginthreadex() + _endthreadex() while (true)
{
printf("main()\n");
}
return ;
}
代码 - 等待线程
#include <stdio.h>
#include <windows.h>
// 线程函数
DWORD WINAPI WorkerThread(LPVOID lpThreadParameter)
{
// 获取传入的参数
int count = (int)lpThreadParameter;
// 循环次数 100
for (int i = ; i < count; ++i)
printf("i = %d\n", i);
return ;
}
int main()
{
// 如何创建一个线程
HANDLE Thread = CreateThread(
NULL, // 安全属性
, // 设置栈的大小,使用默认
WorkerThread, // 表示的是线程的开始位置
(LPVOID), // 线程函数的参数
NULL, // 创建标志
NULL); // 创建出的线程的 Id
// 线程内核对象的信号:
// - 有信号: 当线程运行结束的时候,处于有信号状态
// - 无信号: 当线程正在执行的时候,处于无信号状态
// 等待线程知道线程退出为止
WaitForSingleObject(Thread, INFINITE);
// 主线程一旦退出,子线程也会退出
return ;
}
代码 - 遍历线程
#include <stdio.h>
#include <windows.h>
// 1. 包含头文件
#include <TlHelp32.h>
int main()
{
int Pid = ;
scanf_s("%d", &Pid);
// 2. 拍摄当前所有线程的快照,参数 2 是无效的,传入任何值遍历的都是所有的线程
HANDLE Snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, );
// 3. 检查快照是否创建成功
if (Snapshot == INVALID_HANDLE_VALUE)
{
MessageBox(NULL, L"快照创建失败", L"标题", MB_OK);
ExitThread(-);
}
// 4. 创建结构体用于保存遍历到的信息
THREADENTRY32 ThreadInfo = { sizeof(THREADENTRY32) };
// 5. 尝试遍历到第一个线程信息
if (Thread32First(Snapshot, &ThreadInfo))
{
do {
// [ 判断遍历到的线程所属进程 id 是否为想要遍历的进程 ]
if (Pid == ThreadInfo.th32OwnerProcessID)
{
printf("tid: %d\n", ThreadInfo.th32ThreadID);
}
} while (Thread32Next(Snapshot, &ThreadInfo));
}
return ;
}
代码 - 挂起和恢复
#include <stdio.h>
#include <windows.h>
// 1. 包含头文件
#include <TlHelp32.h>
int main()
{
int Pid = ;
scanf_s("%d", &Pid);
// 2. 拍摄当前所有线程的快照,参数 2 是无效的,传入任何值遍历的都是所有的线程
HANDLE Snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, );
// 3. 检查快照是否创建成功
if (Snapshot == INVALID_HANDLE_VALUE)
{
MessageBox(NULL, L"快照创建失败", L"标题", MB_OK);
ExitThread(-);
}
// 4. 创建结构体用于保存遍历到的信息
THREADENTRY32 ThreadInfo = { sizeof(THREADENTRY32) };
// 5. 尝试遍历到第一个线程信息
if (Thread32First(Snapshot, &ThreadInfo))
{
do {
// [ 判断遍历到的线程所属进程 id 是否为想要遍历的进程 ]
if (Pid == ThreadInfo.th32OwnerProcessID)
{
printf("tid: %d\n", ThreadInfo.th32ThreadID);
// 打开目标线程的句柄
HANDLE Thread = OpenThread(THREAD_ALL_ACCESS, FALSE, ThreadInfo.th32ThreadID);
// 尝试进行挂起, 每调用一次就挂起一次
// SuspendThread(Thread);
// 尝试进行恢复,每调用一次就恢复一次
// ResumeThread(Thread);
// 当挂起计数为 0 的时候,线程就会被调度
// 用于结束标目线程
// TerminateThread(Thread, 0);
}
} while (Thread32Next(Snapshot, &ThreadInfo));
}
return ;
}
代码 - 伪句柄产生的问题
#include <stdio.h>
#include <windows.h>
// 使用伪句柄作为参数传递可能会带来的问题
// 功能函数,通过传入的线程句柄,获取到线程的创建时间
VOID GetThreadCreateTime(HANDLE Thread)
{
// 0. 创建用于保存线程相关时间的结构
FILETIME CreateTime = { }, ExitTime = { };
FILETIME KernelTime = { }, UserTime = { };
// 1. 使用 GetThreadTimes 获取到传入的线程的相关时间
GetThreadTimes(Thread, &CreateTime,
&ExitTime, &KernelTime, &UserTime);
// 2. 将时间转换为本地时间
FILETIME LocalCreateTime = { };
FileTimeToLocalFileTime(&CreateTime, &LocalCreateTime);
// 3. 将时间戳转换为系统时间
SYSTEMTIME SystemTime = { };
FileTimeToSystemTime(&LocalCreateTime, &SystemTime);
// 4. 输出时间
printf("CreateTime: %d 时 %d 分 %d 秒\n", SystemTime.wHour,
SystemTime.wMinute, SystemTime.wSecond);
}
// 线程函数
DWORD WINAPI WorkerThread(LPVOID lpThreadParameter)
{
// 接收传入到线程内的伪句柄
HANDLE Thread = (HANDLE)lpThreadParameter;
// 根据[伪句柄]输出线程的创建时间
// [输出的实际上是自己的创建时间]
GetThreadCreateTime(Thread);
return ;
}
int main()
{
// 获取当前线程的[伪]句柄
HANDLE Thread = GetCurrentThread();
// 1. 查看当前[主]线程的创建时间
GetThreadCreateTime(Thread);
// 2. 等待 2 秒钟,不能保证
Sleep();
// 3. 创建一个新的线程,将伪句柄传入
HANDLE Handle = CreateThread(NULL, , WorkerThread,
(LPVOID)Thread, NULL, NULL);
// 4. 等待线程执行完毕
WaitForSingleObject(Handle, INFINITE);
return ;
}
总结:由于传入的句柄是一个伪句柄,始终指向当前的线程内核对象,所以导致在工作线程内计算出的时间不是主线程的运行时间。线程伪句柄的值始终为【-2】,进程伪句柄的值始终为【-1】
代码 - 真实句柄的获取
// 将伪句柄转换成真实的句柄
DuplicateHandle(
GetCurrentProcess(), // 从哪里拷贝
GetCurrentThread(), // 要拷贝什么
GetCurrentProcess(), // 拷贝到哪里去
&Thread, // 保存拷贝到的句柄
, // 安全访问级别
false, // 是否可以被子进程继承
DUPLICATE_SAME_ACCESS); // 转换选项
线程的退出方式
主线程函数(main\WinMain)返回,最为友好,会调用析构函数、会清理栈
使用ExitThread:不会调用析构函数
使用TerminateThread:不会调用析构函数,不会清理栈
结束进程:可能来不及保存工作结果
线程的优先级
线程只有相对于进程的优先级,随着进程优先级的改变,线程的相对优先级并不会改变
通常情况下手动修改优先级并不会对程序的执行产生变化
最新文章
- 基础DOS命令
- Eclipse中的文件导航插件StartExplorer
- vmware
- Codeforces Round #169 (Div. 2)
- 关于swfupload,客户端中文乱码解决方案!
- javascript中数组常用的方法
- 7、网页制作Dreamweaver(悬浮动态分层导航)
- css笔记——css 实现自定义按钮
- sublime 经验总结 主题有 less2css
- Redis 安装与简单示例(转)
- Selenium IE6 Failed to load the library from temp directory: C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\IED1C1.tmp
- C# TCP多线程服务器示例
- Oracle存储过程 一个具体实例
- CNN在中文文本分类的应用
- Matting任务里的Gradient与Connectivity指标
- 使用 Maven 插件将 class(字节码文件),resource(资源文件),lib(依赖的jar包)分开打包
- sql 中,如何获取两个日期之前月数、周数、天数
- 前端小菜鸡使用Vue+Element笔记(一)
- css卷叶效果
- C++结构变量数据对齐问题
热门文章
- react 项目实战(十)引入AntDesign组件库
- 通过android XML 创建图形,降低对美工的依赖
- Android 4.2 project导入 5.0 SDK Eclipse 开发环境出现的问题总结
- nginx负载均衡向后台传递參数方法(后端也是nginxserver)
- JavaScript 获得代码行号和脚本文件名
- Java学习笔记----你可能不知道那些知识,对象复制与引用
- android中怎么将桌面较长的图标名称显示完整
- YTU 2641: 填空题:静态成员---计算学生个数
- go语言---slice
- ODB(C++ ORM)用Mingw的完整编译过程