第一部分:线程

什么是线程?

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

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

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

创建一个线程

#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. react 项目实战(七)用户编辑与删除

    添加操作列 编辑与删除功能都是针对已存在的某一个用户执行的操作,所以在用户列表中需要再加一个“操作”列来展现[编辑]与[删除]这两个按钮. 修改/src/pages/UserList.js文件,添加方 ...

  2. Js、Jquery对goTop功能的实现

    本文介绍用原生JS和Jquery分别实现的网页goTopbutton功能,以及在这个过程中碰到的问题. 终于实现的效果类似:http://sc2.163.com/home(注意右下角的top) 代码: ...

  3. 省市县三级联动js代码

    省市县三级联动菜单,JS全国省市县(区)联动代码,一般可以用于用户注册或分类信息二手交易网站,需要的朋友直接复制代码就可以用了,不过有朋友反馈说缺少某些城市,具体缺少哪个尚不知,请想用的朋友自己补全吧 ...

  4. Mysql-SQL优化-统计某种类型的个数

    有时我们想统计某种类型有多少个,会用这个SQL. 全表扫描之余,还要filesort.耗时1.34秒. mysql> select country,count(*) from t1 group ...

  5. Ubuntu18.04系统中vi键盘输入字符不匹配

    起因 今天重装了我的雷神笔记本的ubuntu18.04,不要问我为什么,我就是想复习下重装系统而已.好吧,我承认我改错文件启动不起来了. 于是我要重装jdk.maven and so on,但是当我用 ...

  6. 关于mysql建立索引 复合索引 索引类型

    这两天有个非常强烈的感觉就是自己在一些特别的情况下还是hold不住,脑子easy放空或者说一下子不知道怎么去分析问题了,比方,问"hash和btree索引的差别",这非常难吗.仅仅 ...

  7. go15---select

    package main import ( "fmt" ) //go语言提供了一个结构或者形式来帮助处理多个channel的发送和接收问题,这个结构叫做select, //sele ...

  8. JavaScript倒计时类

    (function (){ var jtimer = function() { // init if(arguments.length >= 1) { this.setEndTime(argum ...

  9. Tensorflow学习笔记——占位符和feed_dict(二)

    创建了各种形式的常量和变量后,但TensorFlow 同样还支持占位符.占位符并没有初始值,它只会分配必要的内存.在会话中,占位符可以使用 feed_dict 馈送数据. feed_dict是一个字典 ...

  10. 电脑的系统盘只有10G了

    软件也是缓存多了,准备把一些让家人卸载,昨天开始布置培训任务就是有一个电报的程序,把流程说了下,从今天开始就会指导,错误点分析.