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

news2024/12/23 15:00:39

进程与线程

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

线程:通常被称作但并不真的是轻量级进程或实际工作中的进程,它会真实的执行代码。每个线程都有一个需要执行的代码块称为线程回调函数。每个进程启动的时候会同步启动一个主线程,而主线程所执行的代码块就是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.coloradmin.cn/o/2197430.html

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

相关文章

环形链表(c语言)

1.//环形链表 //输入&#xff1a;head [3,2,0,-4], pos 1 //输出&#xff1a;true //解释&#xff1a;链表中有一个环&#xff0c;其尾部连接到第二个节点。 //输入&#xff1a;head [1, 2], pos 0 //输出&#xff1a;true //解释&#xff1a;链表中有一个环&#xff0c;其…

保留字作为数据表字段导致的问题!!!

我 | 在这里 ⭐ 全栈开发攻城狮、全网10W粉丝、2022博客之星后端领域Top1、专家博主。 &#x1f393;擅长 指导毕设 | 论文指导 | 系统开发 | 毕业答辩 | 系统讲解等。已指导60位同学顺利毕业 ✈️个人公众号&#xff1a;乡下小哥编程。回复 Java全套视频教程 或 前端全套视频教…

考拉悠然携手中国系统打造城市智能中枢,让城市更聪明更智慧

在21世纪的科技浪潮中&#xff0c;智慧城市建设已成为推动城市现代化进程的重要引擎。随着人工智能技术的飞速发展&#xff0c;AI正以前所未有的速度融入智慧城市管理的每一个角落&#xff0c;从交通出行到公共安全&#xff0c;从环境保护到城市管理&#xff0c;无一不彰显着智…

SCRM呼叫中心高保真Axure原型 源文件分享

在数字化时代&#xff0c;客户关系管理&#xff08;CRM&#xff09;对于企业的成功至关重要。SCRM呼叫中心后台作为一款专为CRM设计的软件原型&#xff0c;致力于为企业提供高效、智能的客户沟通解决方案。本文将详细介绍该产品的核心功能及其对企业提升客户满意度和销售业绩的…

《Linux从小白到高手》理论篇:Linux的进程管理详解

本篇将介绍Linux的进程管理相关知识&#xff0c;并将深入介绍Linux的进程间相互通信。 进程就是运行中的程序&#xff0c;一个运行着的程序&#xff0c;可能有多个进程。 比如Oracle DB&#xff0c;启动Oracle实例服务后&#xff0c;就会有多个进程。 Linux进程分类 在 Linux…

如何下载和安装CLion,图文详解

一、下载 登录JetBrains官网&#xff0c;下载最新版本的Clion&#xff0c;Clion目前没有社区版&#xff0c;都是专业版。 二、安装 1、启动Clion安装程序&#xff0c;下一步。 2、修改安装目录&#xff0c;下一步。 3、创建桌面快捷方式&#xff0c;更新PATH变量&#xff0…

Transforms(一)

一、概念 transforms就像是一个工具箱&#xff0c;里面装有很多类 &#xff08;比如&#xff1a;totensor、resize等&#xff0c;可以在结构里查看到一个列表&#xff09;&#xff0c;类就是工具。 但是类不能直接使用&#xff0c;要创建其实例对象之后才能调用&#xff08;to…

国庆假期互联网产品故障事件(神州租车、国航、公邮)盘点

一晃七天假期已经过去了&#xff0c;节后第一天大家股市都赚了盆满钵满吧&#xff0c;盘点一下国庆期间互联网产品故障吧。 一、神州租车&#xff1a; 10 月 2 日&#xff0c;有网友反馈神州租车今日出现服务问题&#xff0c;“App 小程序都崩了”。 对此&#xff0c;神州租车…

【自然语言处理】(2) --Word2Vec实现

文章目录 Word2Vec实现一、训练模型1. 数据预处理2. 构建训练数据3. 搭建word2vec网络&#xff08;CBOW&#xff09;4. 装配设备5. 构建训练模型6. 优化器7. 损失函数8. 迭代模型 二、测试模型1. 预测单词2. 生成词嵌入词典3. 保存训练后的词向量 总结 Word2Vec实现 Word2Vec …

『网络游戏』业务系统基类【08】

创建脚本&#xff1a;SystemRoot.cs 编写脚本&#xff1a;SystemRoot.cs 修改脚本&#xff1a;LoginSys.cs 修改脚本&#xff1a;WindowRoot.cs 运行项目 - 效果相同 本章结束

付费计量系统实体和接口(5)

13.7.2 Sub-classification of the Accounting functions 收费功能的子分级 The Accounting function maintains a current balance of all credit and charge transactions performed in the payment meter. These activities together constitute the Meter Accounting Proce…

小蒋聊技术——DevOps 是什么“玩意”?

时间&#xff1a;2024年 10月 08日 作者&#xff1a;小蒋聊技术 邮箱&#xff1a;wei_wei10163.com 微信&#xff1a;wei_wei10 音频&#xff1a;喜马拉雅 大家好&#xff0c;欢迎来到“小蒋聊技术”&#xff0c;我是小蒋&#xff01;今天我们来聊聊一个时下特别火的概念—…

渐开线起始圆和基圆之间有约束关系吗?

最近看到了一个问题&#xff1a;“渐开线起始圆和基圆之间有约束关系吗&#xff1f;有起始圆要比基圆大某个固定数值这么一说吗&#xff1f;”&#xff0c;这期咱们就来说说这个话题&#xff1a; 如上图所示&#xff1a;一对圆柱齿轮副中&#xff0c;一个齿轮的渐开线起始圆是…

什么是数据编织

What Is Data Fabric? 【dataCamp】 What Is Data Fabric? Data fabric is a unified data architecture that connects disparate data sources, simplifying access and management while ensuring consistency and security across the entire data landscape. Data Fa…

cs61b学习 part3

如果你有许多list&#xff0c;这里将会是大量的时间&#xff0c;我指的是对于单向链表查找时间复杂度O(N)相对于数组O(1)的时间复杂度会慢一些 所以这究竟是顺序表的编写还是链表的改进? IntList public class IntList {public int first;public IntList rest;public IntLis…

webGL进阶(二)物体运动

效果&#xff1a; 模拟时钟效果。 代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewpo…

LSTM变种模型

一、GRU 1.概念 GRU&#xff08;门控循环单元&#xff0c;Gated Recurrent Unit&#xff09;是一种循环神经网络&#xff08;RNN&#xff09;的变体&#xff0c;旨在解决标准 RNN 在处理长期依赖关系时遇到的梯度消失问题。GRU 通过引入门控机制简化了 LSTM&#xff08;长短期…

Python爬虫使用实例-jsyks

目标地址&#xff1a; https://www.jsyks.com/kmy-mnks例如&#xff1a; urlhttps://www.jsyks.com/kmy-mnks # kmy-mnks 科目一-模拟考试 urlhttps://www.jsyks.com/kms-mnks # kms-mnks 科目四-模拟考试一、获取资源 先从本题分析里面得到解析答案【通过div.Exam ul li里面…

面向对象技术——设计模式

目录 层次结构 具体设计模式分类 创建型模式&#xff08;处理创建对象&#xff09; 结构型模式&#xff08;处理类和对象的组合&#xff09; 行为型模式&#xff08;描述类或者对象的交互行为&#xff09; 创建型设计模式 ​编辑 结构型设计模式 行为型设计模式​编辑 …

时序论文17|ICML24 SAMformer:华为新奇视角讨论Transformer时序预测时的收敛优化问题

论文标题&#xff1a;SAMformer: Unlocking the Potential of Transformers in Time Series Forecasting with Sharpness-Aware Minimization and Channel-Wise Attention 论文链接&#xff1a;https://arxiv.org/abs/2402.10198 代码链接&#xff1a;https://github.com/rom…