第一部分:线程

什么是线程?

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

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

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

创建一个线程

#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. C++MFC编程笔记day01 MFC介绍、创建MFC程序和重写消息处理

    一.MFC概念和作用 1.全称Microsoft Foundation Class Library,我们称为微软基础类库,封闭了绝大部分的win32 Api函数,C++语法中的数据结构,程序的运行流程 ...

  2. 复合页( Compound Page )

    复合页(Compound Page)就是将物理上连续的两个或多个页看成一个      独立的大页,它能够用来创建hugetlbfs中使用的大页(hugepage).      也能够用来创建透明大页( ...

  3. Cocos2d-X开发中国象棋《四》设计游戏场景

    设计完開始界面后就要设计游戏界面了 为了理清设计思路先看一张游戏界面效果图 游戏界面设计思路: 1.在窗体上放一张桌子 2.在桌子上放一个棋盘 3.在棋盘右边加入新局button,暂不实现详细的功能 ...

  4. easyUI的treegrid添加节点(append)时间过长,设置等待(wait)遮罩效果

    如题所述,在treegrid中,一次添加多个节点时,时间很长,但easyUI并无显示等待信息,让人以为是陷入了死循环,或者死机了,用户体验很差. 本来,treegrid(或者datagrid)有所谓的 ...

  5. http trigger 事件源是事件的生产者,函数是事件的处理者

    以函数计算作为 API 网关后端服务_用户指南(开放 API)_API 网关-阿里云  https://help.aliyun.com/document_detail/54788.html 创建触发器 ...

  6. go1

    关键字: break default func interface select case defer go map struct chan else goto package switch cons ...

  7. expandableListview的默认箭头箭头怎样移到右边

    1 . ExpandableListView布局:<ExpandableListView    android:id="@+id/bbs_category_expandable_lis ...

  8. 1章 SpringBoot介绍

    新建一个SpringBoot版的Hello World.

  9. 几个SQL小知识(转)

    原文地址:http://www.cnblogs.com/wuguanglei/p/4205976.html 写在前面的话:之前做的一个项目,数据库及系统整体构架设计完成之后,和弟兄们经过一段时间的编码 ...

  10. 1051 复数乘法(C#)

    一.题目内容如下: 复数可以写成 ( 的常规形式,其中 A 是实部,B 是虚部,i 是虚数单位,满足 1:也可以写成极坐标下的指数形式 (,其中 R 是复数模,P 是辐角,i 是虚数单位,其等价于三角 ...