第一部分:线程

什么是线程?

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

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

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

创建一个线程

#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. 结束进程:可能来不及保存工作结果

线程的优先级

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

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

Windows提高_2.1第一部分:线程的更多相关文章

  1. Windows提高_2.3第三部分:内核区同步

    第三部分:内核区同步 等待函数(WaitForObject) 等待函数的形式 单个:WaitForSingleObject 多个:WaitForMultipleObjects 一个可以被等待的对象通常 ...

  2. Windows提高_2.2第二部分:用户区同步

    第二部分:用户区同步 同步和互斥 同步:就是按照一定的顺序执行不同的线程 互斥:当一个线程访问某一资源的时候,其它线程不能同时访问 多线程产生的问题 #include <stdio.h> ...

  3. windows核心编程---第五章 线程的基础

    与前面介绍的进程一样,线程也有两部分组成.一个是线程内核对象.它是一个数据结构,操作系统用它来管理线程以及用它来存储线程的一些统计信息.另一个是线程栈,用于维护线程执行时所需的所有函数参数和局部变量. ...

  4. Windows核心编程 第七章 线程的调度、优先级和亲缘性(下)

    7.6 运用结构环境 现在应该懂得环境结构在线程调度中所起的重要作用了.环境结构使得系统能够记住线程的状态,这样,当下次线程拥有可以运行的C P U时,它就能够找到它上次中断运行的地方. 知道这样低层 ...

  5. <<Windows via C/C++>>学习笔记 —— 线程优先级【转】

    转自:http://www.cnblogs.com/wz19860913/archive/2008/08/04/1259807.html 每个线程都有一个“优先级”,范围是0-31,0为最低优先级,3 ...

  6. windows核心编程---第六章 线程的调度

    每个线程都有一个CONTEXT结构,保存在线程内核对象中.大约每隔20ms windows就会查看所有当前存在的线程内核对象.并在可调度的线程内核对象中选择一个,将其保存在CONTEXT结构的值载入c ...

  7. windows多线程(一) 创建线程 CreateThread

    一 线程创建函数 CreateThread 修改说明: 这里 说了另一种创建线程方法,使用_beginthreadex()更安全的创建线程,在实际使用中尽量使用_beginthreadex()来创建线 ...

  8. windows环境利用semophore机制进行线程同步

    semophore是信号量的意思,常用于PV操作,所谓PV操作就是pend(等待,直到有资源可用,并且消耗资源) V就是释放资源. semophore和mutex区别,mutex本意为互斥,用于线程独 ...

  9. Windows核心编程 第七章 线程的调度、优先级和亲缘性(上)

    第7章 线程的调度.优先级和亲缘性 抢占式操作系统必须使用某种算法来确定哪些线程应该在何时调度和运行多长时间.本章将要介绍Microsoft Windows 98和Windows 2000使用的一些算 ...

随机推荐

  1. 网上Unused Index Script 脚本的问题

    曾经使用过网上下载的脚本查询没有使用过的Index比方SQL SERVER – 2008 – Unused Index Script – Download,事实上如今看起来这个脚本是有一些问题. 脚本 ...

  2. Django打造大型企业官网(七)

    4.13.新闻列表tab栏布局完成 templates/news/index.html <div class="list-outer-group"> <ul cl ...

  3. MySQL-导入与导出

    CSV文件导入MySQL LOAD DATA INFILE语句允许您从文本文件读取数据,并将文件的数据快速导入数据库的表中. 导入文件操作之前,需要准备以下内容: 一.将要导入文件的数据对应的数据库表 ...

  4. 图像处理之基础---基于opencv的灰度图像微分

    argv分别为,可执行文件名.读入的原始图像.输出原始图像的灰度值.输出原始图像灰度值沿x轴方向的一阶微分.输出原始图像灰度值沿x轴方向的二阶微分. #include #include #includ ...

  5. PNG vs. GIF vs. JPEG vs. SVG - When best to use?

    image - PNG vs. GIF vs. JPEG vs. SVG - When best to use? - Stack Overflow https://stackoverflow.com/ ...

  6. YTU 2530: 小勇玩lol

    2530: 小勇玩lol 时间限制: 1 Sec  内存限制: 128 MB 提交: 112  解决: 77 题目描述 小勇是一个忠实的lol玩家,他有自己的战斗力计算方法,每个星期他都会算一下自己的 ...

  7. zoj 3822(概率dp)

    ZOJ Problem Set - 3822 Domination Time Limit: 8 Seconds      Memory Limit: 131072 KB      Special Ju ...

  8. 35. extjs MessageBox里fn:是什么意思

    function的缩写,用来指定回调函数,就是你点击确定或取消按钮之类的按钮以后触发的事件Ext.Msg.show({ title:'自定义消息框', msg:'这是一个自定义消息框,想怎么搞就怎么搞 ...

  9. 观光公交 2011年NOIP全国联赛提高组(贪心,递推)

    观光公交 2011年NOIP全国联赛提高组  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold       题目描述 Description 风景迷人的小城 Y 市 ...

  10. 为什么要用Go语言做后端

    FMZ数字货币量化平台 www.fmz.com, 后端使用Go语言,这里是创始人Zero谈论使用Go语言所带了的便利.原帖地址:https://www.zhihu.com/question/27172 ...