当前位置: 首页 > article >正文

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.kler.cn/news/334609.html

相关文章:

  • sentinel原理源码分析系列(一)-总述
  • Centos Stream 9备份与恢复、实体小主机安装PVE系统、PVE安装Centos Stream 9
  • C++面试速通宝典——9
  • rabbitMq-----消费者管理模块
  • Perforce静态分析工具2024.2新增功能:Helix QAC全新CI/CD集成支持、Klocwork分析引擎改进和安全增强
  • 使用指标进行量化交易时,有哪些需要注意的风险点呢
  • Spring Data JPA中的锁机制
  • CSP-J/S 复赛算法 区间动态规划
  • 【2024年最新】基于springboot+vue的springboot火车订票管理系统lw+ppt
  • Linux学习笔记(七):磁盘的挂载与扩展
  • 鼓组编写:SsdSample鼓映射 GM Map 自动保存 互换midi位置 风格模板 逻辑编辑器
  • 滚雪球学Oracle[1.3讲]:Oracle数据库架构基础
  • 生信初学者教程(二十五):验证候选特征
  • [已解决] Install PyTorch 报错 —— OpenOccupancy 配环境
  • RTR_Chapter_6 下
  • flutter_鸿蒙next_Dart基础①字符串
  • (三)Mysql 数据库系统全解析
  • 探索消息中间件:RabbitMQ深度解析
  • 解锁 SDKMAN!:最新教程与全面简介
  • flume系列之:flume jmx页面导出flume、java进程等全部指标