第一部分:线程

什么是线程?

  • 线程其实可以理解为一段正在执行中的代码,它最少由一个线程内核对象和一个栈组成。

  • 线程之间是没有从属关系的,同一进程下的所有线程都可以访问进程内的所有内容。

  • 主线程其实是创建进程时创建的线程,主线程一旦退出,所有子线程也会退出。

创建一个线程

#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); // 转换选项

线程的退出方式

  1. 主线程函数(main\WinMain)返回,最为友好,会调用析构函数、会清理栈

  2. 使用ExitThread:不会调用析构函数

  3. 使用TerminateThread:不会调用析构函数,不会清理栈

  4. 结束进程:可能来不及保存工作结果

线程的优先级

  • 线程只有相对于进程的优先级,随着进程优先级的改变,线程的相对优先级并不会改变

  • 通常情况下手动修改优先级并不会对程序的执行产生变化

最新文章

  1. 基础DOS命令
  2. Eclipse中的文件导航插件StartExplorer
  3. vmware
  4. Codeforces Round #169 (Div. 2)
  5. 关于swfupload,客户端中文乱码解决方案!
  6. javascript中数组常用的方法
  7. 7、网页制作Dreamweaver(悬浮动态分层导航)
  8. css笔记——css 实现自定义按钮
  9. sublime 经验总结 主题有 less2css
  10. Redis 安装与简单示例(转)
  11. Selenium IE6 Failed to load the library from temp directory: C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\IED1C1.tmp
  12. C# TCP多线程服务器示例
  13. Oracle存储过程 一个具体实例
  14. CNN在中文文本分类的应用
  15. Matting任务里的Gradient与Connectivity指标
  16. 使用 Maven 插件将 class(字节码文件),resource(资源文件),lib(依赖的jar包)分开打包
  17. sql 中,如何获取两个日期之前月数、周数、天数
  18. 前端小菜鸡使用Vue+Element笔记(一)
  19. css卷叶效果
  20. C++结构变量数据对齐问题

热门文章

  1. react 项目实战(十)引入AntDesign组件库
  2. 通过android XML 创建图形,降低对美工的依赖
  3. Android 4.2 project导入 5.0 SDK Eclipse 开发环境出现的问题总结
  4. nginx负载均衡向后台传递參数方法(后端也是nginxserver)
  5. JavaScript 获得代码行号和脚本文件名
  6. Java学习笔记----你可能不知道那些知识,对象复制与引用
  7. android中怎么将桌面较长的图标名称显示完整
  8. YTU 2641: 填空题:静态成员---计算学生个数
  9. go语言---slice
  10. ODB(C++ ORM)用Mingw的完整编译过程