Windows系统编程(三)线程并发

进程与线程

进程:直观的说就是任务管理器中各种正在运行的程序。对于操作系统来说,进程仅仅是一个数据结构,并不会真实的执行代码

线程:通常被称作但并不真的是轻量级进程或实际工作中的进程,它会真实的执行代码。每个线程都有一个需要执行的代码块称为线程回调函数。每个进程启动的时候会同步启动一个主线程,而主线程所执行的代码块就是main函数。当main函数结束时,主线程结束并销毁,同时其他子线程随之销毁

真并发与伪并发

伪并发

在早期的cpu即单核cpu中,因性能核心各方面较为落后,并发编程实际是一个伪并发编程,即系统中所有进程按照优先级去抢占cpu时间片,也就是系统一会执行这个一会执行哪个。

由于抢占时间片所需时间较短,所以我们并不觉得程序卡顿。但各进程抢占cup时间片是一个很麻烦的事情,cpu虽然提供任务切换的功能即TSS任务段,但Windows并不使用。这是因为Windows自己实现了线程调度,即在线程切换时,上个线程代码执行到的地方的线程的状态,线程上下文,通用寄存器,段寄存器,硬件调试寄存器,EIP(指令指针寄存器),EFLAGS等都会被Windows通过Windows(Context)保存,直到再次切换回来后再加载

真并发

随着科技的发展,cpu由单核cpu变成了多核cpu。此时多个核心可以同时独立执行一个 任务,此时也称作真并发

并发形式

1.多进程并发:一个进程里只有一个线程,同时启动多个进程实现并发,如浏览器打开的多个窗口

2.多线程并发:一个进程内运行多个线程,是真实的并发。其中存在变量的访问问题,具体如下:有Value = 100 全局变量以及A,B两个线程。初始时A,B线程访问Value,访问值都是100,现AB两线程都对Value进行++。但操作完成后,Value的值为101,丢失了一个操作。这种情况叫做线程同步问题

线程的生存周期

1.当该线程回调函数执行完毕时,自然死亡

2.当主线程死亡时,子线程被动死亡

并发与并行:并发更强调数量,并行更强调性能

线程应用

普通函数应用

#include<iostream>
#include<thread>
void FirstThreadCallBack() //构建一个普通函数作为子线程
{for (size_t i = 0; i < 100000; i++){std::cout << "First:" << i << std::endl;}
}
int main()
{std::thread obj(FirstThreadCallBack); //声明线程对象,启动一个线程去执行线程回调函数for (size_t i = 0; i < 100000; i++){std::cout << "main:"<< i << std::endl;}system(“pause”);//加上此函数使主线程不会结束,让我们更清晰看到线程并发的过程。否则主线程结束子线程随之结束return 0;
}

此时程序会同时进行上述两个循环打印

仿函数应用

#include<iostream>
#include<thread>
class Exec//一个仿函数
{
public:void operator()()const{std::cout << "Exec" << std::endl;}
};
int main()
{Exec e;std::thread obj(e);return 0;
}

此时打印Exec

Lambda应用

#include<iostream>
#include<thread>
int main()
{std::thread obj([] {std::cout << "Lambda" << std::endl; });return 0;
}

此时程打印Lambda

综上可知,任何可以调用的类型都可以用于线程对象的构造函数传参

线程死亡

一旦线程启动了,我们就需要知道线程是怎么结束的

1.自然死亡:thread析构函数terminate()在子线程执行完毕后析构子线程

2.非自然死亡:thread析构函数执行完毕时,子线程析构,但子线程并没有执行完毕

3.等待:绝对的自然死亡 等待子线程执行完毕后,程序再进行执行

4.不再等待:主线程存活时后台运行,依赖于主线程的存活

5.如果一个线程是Windows原生线程,主线程销毁后其也会死亡

Windows原生线程

现在我们验证一下,当主线程死亡时,Windows原生线程会不会死亡

#include<iostream>
#include<thread>
#include<windows.h>
DWORD ThreadCallBack(LPVOID lpThreadParameter)
{for (size_t i = 0; i < 100000; i++){std::cout << "First:" << i << std::endl;}return 0;
}
int main()
{CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ThreadCallBack, NULL, NULL, NULL);//创建了一个windows原生线程return 0;
}

此时运行程序,发现随着主线程的结束该Windows原生线程死亡

等待死亡

#include<iostream>
#include<thread>
#include<windows.h>
DWORD ThreadCallBack(LPVOID lpThreadParameter)
{for (size_t i = 0; i < 100000; i++){std::cout << "First:" << i << std::endl;}return 0;
}
int main()
{HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ThreadCallBack, NULL, NULL, NULL);//创建一个原生的Windows线程WaitForSingleObject(hThread, -1); //此时主线程会永久等待该子线程结束以后再结束return 0;
}

此时运行程序原生线程不会死亡,直到它运行完毕

阻塞等待

#include<iostream>
#include<thread>
#include<windows.h>
DWORD ThreadCallBack(LPVOID lpThreadParameter)
{for (size_t i = 0; i < 100000; i++){std::cout << "First:" << i << std::endl;}return 0;
}
int main()
{std::thread obj(FirstThreadCallBack);//创建一个普通的线程obj.join(); //阻塞等待,作用是在此处等待子线程结束,程序再继续运行。//当使用此函数时,我们通常需要加一个异常处理。这是因为子线程可能会出现一个异常报错而导致无法执行完毕以至于程序一直处于阻塞等待的情况return 0;
}

此时运行程序,知道子线程运行完毕,主线程才会结束

不再等待

#include<iostream>
#include<thread>
#include<windows.h>
DWORD ThreadCallBack(LPVOID lpThreadParameter)
{for (size_t i = 0; i < 100000; i++){std::cout << "First:" << i << std::endl;}return 0;
}
int main()
{std::thread obj(FirstThreadCallBack);obj.detach(); //不再等待:同Windows原生线程一样,主线程死亡,其子线程也死亡 
//此时额外加一个循环,程序在执行该循环时,主线程没有死亡,子线程也不会死亡,而是一起执行两个线程for (size_t i = 0; i < 100000; i++) {std::cout << "main:" << i << std::endl;}return 0;
}

线程同步问题

问题演示

如下当我们演示一个简单的线程同步

#include<iostream>
#include<thread>
#include<windows.h>
#include<string.h>
void Print(std::string szBuffer,int nCount)
{for (size_t i = 0; i < nCount; i++){std::cout << szBuffer << ":" << i << std::endl;}
}
int main()
{std::thread obj(Print,"abc",200);system(“pause”);return 0;
}

程序运行发现:

原因:这就是时间切片的伪并发可能出现的问题,很形象展示了线程同步问题这个现象

现我们针对如下线程同步程序进行进一步的问题解决讲解

#include <iostream>
#include <thread>
int g_Value = 0; 
void add()
{for (size_t i = 0; i < 1000000; i++){g_Value++;}
}
int main()
{std::thread objA(add);std::thread objB(add);objA.join();objB.join();std::cout << g_Value << std::endl;system("pause");return 0;
}

程序运行以后,g_Value的最终结果应该是2000000,但但每次运行时g_Value都是随机数,这是因为在线程同步时出现丢失操作

互斥体解决线程同步问题

方法一:使用互斥体方法

#include <iostream>
#include <thread>
#include<mutex>
int g_Value = 0; 
std::mutex some_mutex; //声明一个互斥体,用于线程可能出错的地方
void add()
{for (size_t i = 0; i < 1000000; i++){some_mutex.lock(); //该函数被互斥体加锁保护。当一个线程在访问该函数时,其他线程无法访问g_Value++; some_mutex.unlocke(); //互斥体解锁}
}//此时该函数不会再出现多线程同时访问的问题了
int main()
{std::thread objA(add);std::thread objB(add);objA.join();objB.join();std::cout << g_Value << std::endl;system("pause");return 0;
}

方法二:使用锁类模板

#include <iostream>
#include <thread>
#include<mutex>
int g_Value = 0; 
void add()
{for (size_t i = 0; i < 1000000; i++){//构造函数调用时加锁,析构函数调用时解锁std::lock_guard<std::mutex> guard(some_mutex); g_Value++;}
}
int main()
{std::thread objA(add);std::thread objB(add);objA.join();objB.join();std::cout << g_Value << std::endl;system("pause");return 0;
}

以上两种方法可以很好的解决线程同步问题

作业

01.尝试使用多线程造成线程同步问题。
02.尝试使用thread库中的其他控制函数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/881526.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设计模式之适配器模式(Adapter)

一、适配器模式介绍 适配器模式(adapter pattern )的原始定义是&#xff1a;将类的接口转换为客户期望的另一个接口&#xff0c; 适配器可以让不兼容的两个类一起协同工作。 适配器模式是用来做适配&#xff0c;它将不兼容的接口转换为可兼容的接口&#xff0c;让原本由于接口…

2024年1月Java项目开发指南18:自定义异常输出

一般情况下&#xff0c;报错信息一大堆&#xff0c;值得注意的只有三个地方&#xff1a; 哪个文件发生了错误哪一行发生了错误错误原因是什么 只要知道这三个东西就能快速的定位到错误发生的位置并且根据提示解决。 如果你也喜欢我的这种异常输出(如文章顶部图) 那么可以参考以…

[M数学] lc3164. 优质数对的总数 II(因数分解+倍增+推公式+思维+好题)

文章目录 1. 题目来源2. 题目解析 1. 题目来源 链接&#xff1a;3164. 优质数对的总数 II 2. 题目解析 挺不错的一道 因数分解、倍增 的题目&#xff0c;需要一定的思维和推公式的能力才能解决。灵神的题解已经非常清晰易懂了&#xff0c;可以直接去看。 倍增思路&#xff…

大数据分析案例-基于逻辑回归算法构建抑郁非抑郁推文识别模型

🤵‍♂️ 个人主页:@艾派森的个人主页 ✍🏻作者简介:Python学习者 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞👍🏻 收藏 📂加关注+ 喜欢大数据分析项目的小伙伴,希望可以多多支持该系列的其他文章 大数据分析案例合集

(十八)、登陆 k8s 的 kubernetes-dashboard 更多可视化工具

文章目录 1、回顾 k8s 的安装2、确认 k8s 运行状态3、通过 token 登陆3.1、使用现有的用户登陆3.2、新加用户登陆 4、k8s 可视化工具 1、回顾 k8s 的安装 Mac 安装k8s 2、确认 k8s 运行状态 kubectl proxy kubectl cluster-info kubectl get pods -n kubernetes-dashboard3、…

如何启动一个OpenSearch

创建两个集群&#xff0c;标注 不含备用节点 选择集群版本和配置集群版本 冷热存储和专用主节点这个按需开启 然后是网络&#xff0c;是否使用自定义域名&#xff0c;集群开在VPC还是公网上。 选择是否开启认证&#xff1a; 访问策略&#xff0c;其实就是资源策略 维护时段…

Oracle 数据库安装和配置详解

Oracle 数据库安装和配置详解 Oracle 数据库是一款功能强大、广泛使用的企业级关系数据库管理系统 (RDBMS)&#xff0c;适用于处理大型数据库和复杂事务。本文将介绍如何在 Linux 和 Windows 环境下安装 Oracle 数据库&#xff0c;并对其进行基本配置&#xff0c;帮助开发者快…

理解计算机系统_程序的机器级表示(特别篇):对比move和leaq看汇编语言中数据的传递,变量和指针的映射,指针的实现

前言 以<深入理解计算机系统>(以下称“本书”)内容为基础&#xff0c;对程序的整个过程进行梳理。本书内容对整个计算机系统做了系统性导引,每部分内容都是单独的一门课.学习深度根据自己需要来定 引入 汇编代码传递数据的机制令人费解.而这部分内容又是比较基础的,需要…

实例详解 | 借助 Langchain 和 Gemma 2 构建 RAG 应用

本文原作者&#xff1a;Connie Leung&#xff0c;谷歌开发者专家 (GDE)&#xff0c;原文发布于&#xff1a;DEV Community https://dev.to/railsstudent/build-a-rag-application-to-learn-angular-using-langchhtainjs-nestjs-htmx-and-gemma-2-5ggk 本文将为您介绍如何使用…

.Net基础1

.NET框架 项目结构 Connected Services是第三方服务MVC框架appsettings.json配置文件Program.cs控制台应用程序Properties里的json文件是配置启动方式 1. 基本开发 出现这个bug是因为防火墙没有把浏览器加入白名单&#xff0c;可以暂时先用http启动代替 第一步创建控制器&am…

数字化AI新赋能,智享AI直播:开启一个全新的直播时代!

数字化AI新赋能&#xff0c;智享AI直播&#xff1a;开启一个全新的直播时代! 在科技日新月异的今天&#xff0c;人工智能&#xff08;AI&#xff09;技术正以前所未有的速度改变着我们的生活和工作方式。其中&#xff0c;AI直播技术的崛起&#xff0c;无疑是数字化时代的一大亮…

深入理解 C/C++ 指针

深入理解 C 指针&#xff1a;指针、解引用与指针变量的详细解析 前言 在 C 编程语言中&#xff0c;指针 是一个非常强大且重要的概念。对于初学者来说&#xff0c;指针往往会让人感到困惑不解。本文将通过形象的比喻&#xff0c;帮助大家深入理解指针、解引用与指针变量的概念…

ELK:Elasticsearch、Logstash、Kibana Spring Cloud Sleuth和Spring Cloud Zipkin

〇、虚拟机中docker安装elasticsearch 、Kibana、Logstash elasticsearch导入中文分词器 Logstash修改es数据库ip及创建索引名配置 一、elasticsearch数据库的结构 和mysql作比较&#xff0c;mysql中的数据库的二维表相当于es数据库的index索引结构&#xff1b;mysql数据库的二…

大数据处理从零开始————4.认识HDFS分布式文件系统

1.分布式文件系统HDFS 1.1 认识HDFS 当单台服务器的存储容量和计算性能已经无法处理大文件时&#xff0c;分布式文件系统应运而生。什么是分布式系统&#xff0c;分布式系统是由多个独立的计算机或节点组成的系统&#xff0c;这些计算机通过网络连接&#xff…

Mysql(五) --- 数据库设计

文章目录 前言1.范式1.1.第一范式1.1.1 定义1.1.2.例子 1.2.第二范式1.2.1 定义1.2.2 例子1.2.3.不满足第二范式可能会出现的问题 1.3.第三范式1.3.1 定义2.3.2 示例 2. 设计过程3. 实体-关系图3.1 E-R图的基本组成3.2 关系的类型3.2.1 一对一关系(1:1)3.2.2 ⼀对多关系(1:N)3.…

paimon,基础查询语句测试

基础设置 -- 创建catalog/加载catalog&#xff0c;如果这个catalog已经存在就不会创建&#xff0c;自动加载元数据信息CREATE CATALOG fs_paimon_catalog WITH ( type paimon, warehouse hdfs://wsl01:8020/paimon/catalog ); -- 使用catalog use catalog fs_paimon_catalog…

Java虚拟机(JVM)

目录 内存区域划分堆&#xff08;Heap&#xff09;方法区&#xff08;Method Area&#xff09;程序计数器&#xff08;Program Counter Register&#xff09;虚拟机栈&#xff08;VM Stack&#xff09;本地方法栈&#xff08;Native Method Stack&#xff09; 类加载的过程类加…

[C++]使用纯opencv部署yolov11-seg实例分割onnx模型

【算法介绍】 在C中使用纯OpenCV部署YOLOv11-seg进行实例分割是一项具有挑战性的任务&#xff0c;因为YOLOv11通常是用PyTorch等深度学习框架实现的&#xff0c;而OpenCV本身并不直接支持加载和运行PyTorch模型。然而&#xff0c;可以通过一些间接的方法来实现这一目标&#x…

运维工具之ansible

Ansible 1.什么是ansible? ​ ansible是基于ssh架构的自动化运维工具&#xff0c;由python语言实现&#xff0c;通过ansible可以远程批量部署等。 2.部署前提 ​ 控制端需要安装ansible,被控制端要开启ssh服务&#xff0c;并允许远程登录&#xff0c;被管理主机需要安装py…

卸载PLSQL及标准卸载流程

目录 1. 卸载PLSQL2. 删除注册表3. 删除数据信息 1. 卸载PLSQL 等待进度条走完 2. 删除注册表 regedit 右击删除 3. 删除数据信息 由于AppData是隐藏文件&#xff0c;需要勾选隐藏的项目。 重启电脑&#xff0c;PLSQL就卸载成功了。