Windows提高_2.1第一部分:线程
第一部分:线程
什么是线程?
线程其实可以理解为一段正在执行中的代码,它最少由一个线程内核对象和一个栈组成。
线程之间是没有从属关系的,同一进程下的所有线程都可以访问进程内的所有内容。
主线程其实是创建进程时创建的线程,主线程一旦退出,所有子线程也会退出。
创建一个线程
#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:不会调用析构函数,不会清理栈
结束进程:可能来不及保存工作结果
线程的优先级
线程只有相对于进程的优先级,随着进程优先级的改变,线程的相对优先级并不会改变
通常情况下手动修改优先级并不会对程序的执行产生变化
Windows提高_2.1第一部分:线程的更多相关文章
- Windows提高_2.3第三部分:内核区同步
第三部分:内核区同步 等待函数(WaitForObject) 等待函数的形式 单个:WaitForSingleObject 多个:WaitForMultipleObjects 一个可以被等待的对象通常 ...
- Windows提高_2.2第二部分:用户区同步
第二部分:用户区同步 同步和互斥 同步:就是按照一定的顺序执行不同的线程 互斥:当一个线程访问某一资源的时候,其它线程不能同时访问 多线程产生的问题 #include <stdio.h> ...
- windows核心编程---第五章 线程的基础
与前面介绍的进程一样,线程也有两部分组成.一个是线程内核对象.它是一个数据结构,操作系统用它来管理线程以及用它来存储线程的一些统计信息.另一个是线程栈,用于维护线程执行时所需的所有函数参数和局部变量. ...
- Windows核心编程 第七章 线程的调度、优先级和亲缘性(下)
7.6 运用结构环境 现在应该懂得环境结构在线程调度中所起的重要作用了.环境结构使得系统能够记住线程的状态,这样,当下次线程拥有可以运行的C P U时,它就能够找到它上次中断运行的地方. 知道这样低层 ...
- <<Windows via C/C++>>学习笔记 —— 线程优先级【转】
转自:http://www.cnblogs.com/wz19860913/archive/2008/08/04/1259807.html 每个线程都有一个“优先级”,范围是0-31,0为最低优先级,3 ...
- windows核心编程---第六章 线程的调度
每个线程都有一个CONTEXT结构,保存在线程内核对象中.大约每隔20ms windows就会查看所有当前存在的线程内核对象.并在可调度的线程内核对象中选择一个,将其保存在CONTEXT结构的值载入c ...
- windows多线程(一) 创建线程 CreateThread
一 线程创建函数 CreateThread 修改说明: 这里 说了另一种创建线程方法,使用_beginthreadex()更安全的创建线程,在实际使用中尽量使用_beginthreadex()来创建线 ...
- windows环境利用semophore机制进行线程同步
semophore是信号量的意思,常用于PV操作,所谓PV操作就是pend(等待,直到有资源可用,并且消耗资源) V就是释放资源. semophore和mutex区别,mutex本意为互斥,用于线程独 ...
- Windows核心编程 第七章 线程的调度、优先级和亲缘性(上)
第7章 线程的调度.优先级和亲缘性 抢占式操作系统必须使用某种算法来确定哪些线程应该在何时调度和运行多长时间.本章将要介绍Microsoft Windows 98和Windows 2000使用的一些算 ...
随机推荐
- dorker 安装
http://www.docker.org.cn/book/install/install-docker-win7-win8-requirements-38.html1. 你先去下载Docker To ...
- [swift实战入门]手把手教你编写2048(一)
苹果设备越来越普及,拿着个手机就想捣鼓点啥,于是乎就有了这个系列,会一步一步教大家学习swift编程,学会自己做一个自己的app,github地址:https://github.com/scarlet ...
- python 爬虫必知必会
#python爬虫 #新闻数据 #机器学习:股票数据获取及分析 #网络搜索引擎的一个部件 #Http协议 #正则表达式 #多线程,分布式 #http报文展示 #Http 应答报文介绍 #1.应答码 # ...
- SpringMVC 运行过程
加入jar包 在web.xml中配置DispatherServlet 加入SpringMVC配置文件 编写处理请求的处理器,并表示为处理器 编写视图 可见的运行流程: 实际的运行流程:
- 预载入和javascript对象
请参见 http://www.west263.com/info/html/wangyezhizuo/Javascript/20080225/34168.html
- ASP.NET和C#的区别/
1..NET是一个平台,一个抽象的平台的概念. .NET平台其本身实现的方式其实还是库,抽象层面上来看是一个平台. 个人理解.NET核心就只是.NET Framework. .NET Framewor ...
- LOJ 6089 小Y的背包计数问题 —— 前缀和优化DP
题目:https://loj.ac/problem/6089 对于 i <= √n ,设 f[i][j] 表示前 i 种,体积为 j 的方案数,那么 f[i][j] = ∑(1 <= k ...
- bzoj1016 [JSOI2008]最小生成树计数——Kruskal+矩阵树定理
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1016 从 Kruskal 算法的过程来考虑产生多种方案的原因,就是边权相同的边有一样的功能, ...
- bzoj 1592: [Usaco2008 Feb]Making the Grade 路面修整【dp】
因为是单调不降或单调不升,所以所有的bi如果都是ai中出现过的一定不会变差 以递增为例,设f[i][j]为第j段选第i大的高度,预处理出s[i][j]表示选第i大的时,前j个 a与第i大的值的差的绝对 ...
- phpAnalysis调试接口
phpAnalysis是一款轻量级非侵入式PHP应用性能分析器,适用于开发.测试及生产环境部署使用,方便开发及测试工程师诊断性能问题: 通过tideways收集PHP程序单步运行过程中所有的函数调用时 ...