0x01. 如何让线程停下来

让自己停下来:

Sleep() 函数

当程序执行到某段代码的时候可以使用sleep() 函数进行暂停

使用sleep()函数挂起的时候会自动恢复过来的

让别人停下来:

SuspendThread() 函数

使用这个函数挂起,也就是阻塞的时候,必须使用ResumeThread()函数来恢复

线程恢复:

ResumeThread() 函数

我们先讲Sleep函数:

以上章代码为例子:

#include <stdio.h>
#include <windows.h> DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
for (int i = 0; i < 100; i++)
{
Sleep(500);
printf("++++++++++++++ %d \n", i); } return 0;
} int main()
{
int n;
n = 10;
HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL); Sleep(5000); //main函数这个线程睡眠五秒
SuspendThread(hThread); //挂起线程,也就是阻塞状态
Sleep(5000); //再睡眠5秒
ResumeThread(hThread); //恢复线程 CloseHandle(hThread); //关闭线程句柄
getchar();
return 0;
}

注意:关闭线程的话要放在下面,不然无法控制。因为线程是时间型,你执行之后关闭句柄,线程还是在执行,但是执行完代码之后就关闭了

然而需要注意的是线程挂起几次,就需要使用函数ResumeThread()恢复


int main()
{
int n;
n = 10;
HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL); Sleep(5000); //main函数这个线程睡眠五秒
SuspendThread(hThread); //挂起线程,也就是阻塞状态
SuspendThread(hThread);
Sleep(5000);
ResumeThread(hThread);
ResumeThread(hThread); CloseHandle(hThread);
getchar();
return 0;
}

sleep的话睡眠之后是会自动启动,使用SuspendThread()函数是需要另外ResumeThread()恢复的

当线程执行做另外一件事的时候,怎么知道线程执行没执行完毕呢?

需要用到两个函数

1、WaitForSingleObject()

2、WaitForMultipleObjects()

第一个参数是句柄

dwMilliseconds : 等待的最长时间,时间终了,即使handle尚未成为激发状态,此函数还是要返回。

此值可以是0(代表立即返回),也可以是INFINITE(代表无穷等待)。

可以看出它一直在不断的跑,直到线程发生变化,这个函数就往下走了,我们接下来可以看看是不是把线程都执行完了,才打印那段话

#include <stdio.h>
#include <windows.h> DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
for (int i = 0; i < 30; i++)
{
Sleep(50);
printf("++++++++++++++ %d \n", i); } return 0;
} int main()
{
int n;
n = 10;
HANDLE hThread[2];
hThread[0] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
hThread[1] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL); WaitForSingleObject(hThread, INFINITE);
printf("线程执行完毕 \n"); //Sleep(5000); //main函数这个线程睡眠五秒
//SuspendThread(hThread); //挂起线程,也就是阻塞状态
//Sleep(5000);
//ResumeThread(hThread); CloseHandle(hThread);
getchar();
return 0;
}

再次修改一下代码,再创建一个线程,线程是可以同一段代码,但是它们是两个堆栈,互相没关系的

当这两个线程执行完毕的时候,我们再执行 线程执行完毕 这段代码

那么这边就用到了新的 API,也就是WaitForMultipleObjects()

DWORD WaitForMultipleObjects(
DWORD nCount, //等几个内核对象
const HANDLE *lpHandles, //内核对象数组
BOOL bWaitAll, //等待模式
DWORD dwMilliseconds //等待时间,想一直等待的话 INFINITE
);

等待模式是什么?

可以指定所有等待对象状态都发生变更的时候才返回,也可以指定任何一个对象发生变更就返回

True:这个函数当所有对象都发生改变,才返回

补充

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
for (int i = 0; i < 30; i++)
{
Sleep(50);
printf("++++++++++++++ %d \n", i); } return 0;
}

比如说这边,为什么会有个DWORD,因为我们可以看这个线程返回值,执行成功的话返回什么,执行失败的话返回什么

具体可以根据项目需求来定

这时候可以使用另外一个函数,GetExitCodeThread()函数

第一个就是Handle,也就是句柄,我们把刚刚的两个线程句柄放进去就行了

第二个参数就是接收的参数,IN 是输入;_OUT_是输出,这时候我们可以定义两个变量来接收这个参数

完整代码如下:

#include <stdio.h>
#include <windows.h> DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
for (int i = 0; i < 30; i++)
{
Sleep(50);
printf("++++++++++++++ %d \n", i); } return 0;
} DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
for (int i = 0; i < 30; i++)
{
Sleep(50);
printf("++++++++++++++ %d \n", i); } return 1;
}
int main()
{
HANDLE arrhThread[2];
DWORD dwResult1;
DWORD dwResult2; arrhThread[0] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
arrhThread[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL); WaitForMultipleObjects(2, arrhThread, TRUE, INFINITE);
printf("线程执行完毕 \n");
GetExitCodeThread(arrhThread[0], &dwResult1);
GetExitCodeThread(arrhThread[1], &dwResult2); CloseHandle(arrhThread[0]);
CloseHandle(arrhThread[1]);
getchar();
return 0;
}

小Tips:

如果你当前的电脑只有一个核,当你的A线程跑一半,切换到了B线程,A线程怎么办?

因为程序跑的时候需要一堆寄存器,比如eax,ebx,ecx,edx ,那A线程要不要保留呢?

其实丢不了,每个线程都有一个结构体,当自己被切换的时候,会把当前运行情况,寄存器里面的值存到结构体

这个结构体叫CONTEXT;

然后我们跟进去,可以发现一大堆的寄存器

接下来我们就可以写代码,体会一下CONTEXT;

因为这个结构体里面好多寄存器,所以微软给了个方便的方法,那就是一段一段的获取

比如我们想要这一段的寄存器

context.ContextFlags = CONTEXT_INTEGER;

我们可以这样写,就更方便了,想看其他段就把 CONTEXT_INTEGER 改成那个段对应的参数

设置、获取线程上下文

通过上面我们已经赋好值了,代码如下:

#include <stdio.h>
#include <windows.h> DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
for (int i = 0; i < 30; i++)
{
Sleep(50);
printf("++++++++++++++ %d \n", i); } return 0;
} DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
for (int i = 0; i < 30; i++)
{
Sleep(50);
printf("++++++++++++++ %d \n", i); } return 1;
}
int main()
{
HANDLE arrhThread[2];
DWORD dwResult1;
DWORD dwResult2; arrhThread[0] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
arrhThread[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL); SuspendThread(arrhThread[0]);
CONTEXT context;
context.ContextFlags = CONTEXT_INTEGER; CloseHandle(arrhThread[0]);
CloseHandle(arrhThread[1]);
getchar();
return 0;
}

我们需要用到新的API来获取上下文

1、GetThreadContext() //获取值

BOOL WINAPI GetThreadContext(
_In_ HANDLE hThread, //线程句柄
_Inout_ LPCONTEXT lpContext //指针
);

2、SetThreadContext() //设置修改值

BOOL WINAPI SetThreadContext(
_In_ HANDLE hThread,
_In_ CONST CONTEXT * lpContext
);
int main()
{
HANDLE arrhThread[2];
DWORD dwResult1;
DWORD dwResult2; arrhThread[0] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
//arrhThread[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL); SuspendThread(arrhThread[0]);
CONTEXT context;
context.ContextFlags = CONTEXT_INTEGER;
GetThreadContext(arrhThread[0], &context);
printf("%#x %#x\n", context.Eax, context.Ebx);
ResumeThread(arrhThread[0]); CloseHandle(arrhThread[0]);
//CloseHandle(arrhThread[1]);
getchar();
return 0;
}

我们获取的话也可以修改,但是我们就打印一下就可以了

成功得到了寄存器的值

这时候就可以知道为什么一个核也可以跑那么快了,因为线程被切换会把寄存器数据存入结构体,当个再次启用的时候

再把结构体的值取出来,放到当前CPU的寄存器里

总结:

Win32编程之控制线程的更多相关文章

  1. (十) 一起学 Unix 环境高级编程 (APUE) 之 线程控制

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

  2. Unix环境高级编程(十二)线程控制

    本章介绍了一个进程中多个线程之间如何保持数据的似有性及进程的系统调用如何与线程进行交互. 1.线程限制: Single Unix定义了一线线程操作的限制,和其他的限制一样,可以通过sysconf来查询 ...

  3. (九) 一起学 Unix 环境高级编程 (APUE) 之 线程

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

  4. win32编程简介

    win32编程简介 复习Win32整理下知识. 为什么学习win32? 我们要编写windos程序.都离不开API. 也就是我们所说的win32程序. 所以学好win32是你能不能再windows下编 ...

  5. Java并发编程:Java线程池核心ThreadPoolExecutor的使用和原理分析

    目录 引出线程池 Executor框架 ThreadPoolExecutor详解 构造函数 重要的变量 线程池执行流程 任务队列workQueue 任务拒绝策略 线程池的关闭 ThreadPoolEx ...

  6. GPU编程自学5 —— 线程协作

    深度学习的兴起,使得多线程以及GPU编程逐渐成为算法工程师无法规避的问题.这里主要记录自己的GPU自学历程. 目录 <GPU编程自学1 -- 引言> <GPU编程自学2 -- CUD ...

  7. ROS Learning-028 (提高篇-006 A Mobile Base-04) 控制移动平台 --- (Python编程)控制虚拟机器人的移动(不精确的制定目标位置)

    ROS 提高篇 之 A Mobile Base-04 - 控制移动平台 - (Python编程)控制虚拟机器人的移动(不精确的制定目标位置) 我使用的虚拟机软件:VMware Workstation ...

  8. Python学习笔记整理总结【网络编程】【线程/进程/协程/IO多路模型/select/poll/epoll/selector】

    一.socket(单链接) 1.socket:应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socke ...

  9. Java并发编程系列-(2) 线程的并发工具类

    2.线程的并发工具类 2.1 Fork-Join JDK 7中引入了fork-join框架,专门来解决计算密集型的任务.可以将一个大任务,拆分成若干个小任务,如下图所示: Fork-Join框架利用了 ...

随机推荐

  1. vue学习09 图片切换

    目录 vue学习09 图片切换 定义图片数组:imgList:[],列表数据使用数组保存 添加图片索引:index 绑定src属性:使用v-bind,v-bind指令可以设置元素属性,比如src 图片 ...

  2. 20190531模拟赛总结&反思

    T1: 来源:Codeforces -  Classroom Watch Describe: 给出一个正整数 n,现在问存在多少个 x,使得  x在十进制下的每一位之和加上 x 等于 n. Solut ...

  3. 使用MATLAB 2019 App Design 工具设计一个 电子日记App

    使用MATLAB 2019 App Design 工具设计一个 电子日记App1.1 前言:由于信号与系统课程需要,因此下载了MATLAB软件,加之对新款的执着追求,通过一些渠道,下载了MATLAB ...

  4. Spring Boot(二) :Redis 使用

    Redis 介绍 Redis 是目前业界使用最广泛的内存数据存储.相比 Memcached,Redis 支持更丰富的数据结构,例如 hashes, lists, sets 等,同时支持数据持久化.除此 ...

  5. 【PowerQuery】做了一万遍的工资条

    前面已经了解了Excel.VBA.Python实现工资条,今天尝试用PQ做一遍 做之前迷惑了很久,如何能自定义长度 Table有Repeat函数,但是List没有.看来另外想办法 一步步接近目标  请 ...

  6. module(JS模块系统)

    JS - module(模块系统) 重新学习ES6 倒数第一章 module 什么是module? 百度的解释 之前接触过AngularJS,现在看Dojo,都有对模块的使用.在dojo官网看到这段文 ...

  7. tensorflow(一):基础

    一.张量 1.张量的概念 在TensorFlow中,所有的数据都通过张量的形式来表示.从功能的角度,张量可以简单理解为多维数组,零阶张量表示标量(scalar),也就是一个数:一阶张量为向量(vect ...

  8. JS寄快递地址智能解析

    JS寄快递地址智能解析--2020年7月15日 去年做了些前端内容,最近在整理一些稍微有点用的内容,比如智能解析地址,用户只要输入:张三1351111111江苏省扬州市广陵区XX小区X楼xxx室,就能 ...

  9. 记录一下第一次写 50行 SQL代码

    这 是一个电商项目,做的是报表的展示,我还以为要请求几次,结果,用一个SQL全部查完了 下面是目标效果图 这是我的SQL代码 SELECT product.NAME, product.price, p ...

  10. windows 漏洞列表

    漏洞列表 #Security Bulletin   #KB     #Description    #Operating System CVE-2017-0213 [Windows COM Eleva ...