【Linux】第十八站:进程等待

文章目录

  • 一、进程等待的必要性
    • 1.进程等待是什么
    • 2.进程等待的必要性
    • 3.为什么要进程等待呢?
  • 二、进程等待的方法
    • 1.问题
    • 2.wait
    • 3.waitpid
    • 4.status的原理
    • 5.等待失败
    • 6.与status有关的两个宏
    • 7.options

一、进程等待的必要性

1.进程等待是什么

通过系统调用wait/waitpid,来进行对子进程状态检测与回收的功能!

2.进程等待的必要性

  • 子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

3.为什么要进程等待呢?

答案已经在前面回答了

就是因为僵尸进程无法被杀死,需要通过进程等待来杀掉它,进而解决内存泄漏问题----必须解决的

我们要通过进程等待,获得子进程的退出情况,要能够知道布置给子进程的任务,它完成的怎么样了----要么关心,也可能不关心

二、进程等待的方法

1.问题

我们使用如下代码

#include<unistd.h>    
#include<stdlib.h>    
#include<stdio.h>
int main()    
{    pid_t id = fork();    if(id < 0)                                                                {                                          perror("fork:");    return 1;    }         else if(id == 0)    {                     int cnt = 5;    while(cnt)    {                   printf("I am child,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);    cnt--;    sleep(1);    }    exit(0);        }                 else     {                                                                               while(1)      {    printf("I am parent,pid:%d,ppid:%d\n",getpid(),getppid());    sleep(1);    }    }    return 0;                                                                                                                                                                                  
}

上述代码的功能不难理解。然后我们使用监控去运行一下

while :; do ps ajx | head -1 && ps ajx | grep -v grep |grep waitTest; echo "---------------------------------"; sleep 1;done 

运行结果如下所示

image-20231116145041367

为了解决上面的问题,我们可以让父进程通过调用wait/waitpid进行僵尸进程的回收问题!

2.wait

我们先来看wait函数

image-20231116145824190

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);

返回值:

  • 成功返回被等待进程pid,失败返回-1。

参数:

  • 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

这个系统调用的作用就是等待一个进程,直到它的状态发生改变。

对于wait函数,我们可以看到它需要传入一个指针,但是在wait函数,我们先不关心它的这个参数。也就是我们先给他一个NULL指针

这个wait它会返回一个值,这个值就是对应等待的子进程的pid

#include<stdio.h>                                                                                           
#include<unistd.h>                                                                                      
#include<stdlib.h>                                                                    
#include<sys/types.h>                                                                           
#include<sys/wait.h>                                                                                    
int main()                                                           
{                                                                                        pid_t id = fork();                                                                  if(id < 0)                                                                   {                                                                        perror("fork:");                                                                       return 1;                                                                }                                                                 else if(id == 0)                                                                        {                                                                   int cnt = 5;                                                                  while(cnt)                                                                 {                                                             printf("I am child,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);           cnt--;                                                                 sleep(1);                                                                    }                                                                      exit(0);                                                                            }                                                                    else                                                                        {        int cnt = 10;                                                                             while(cnt)                                                                  {                                                                         printf("I am parent,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);                        cnt--;                                                                        sleep(1);                                                                         }                                                                                          pid_t ret = wait(NULL);                                                                         if(ret == id)                                                                                  {                                                                                        printf("wait success:,ret : %d\n",ret);                        }                                                                      sleep(5);                                                                                         }                                                                                                                                                      return 0;                                                
} 

最终它的运行结果为

程序一共经历了三个5秒。第一个五秒钟,是父子进程都存在,第二个五秒,子进程进入僵尸状态。第三个五秒,子进程的僵尸状态被回收了。

image-20231116151813720

所以说,到目前为止,进程等待是必须的。因为这个进程等待最重要的作用就是回收僵尸进程

那么如果我们这个程序有很多个子进程呢?应该等待哪一个呢?其实wait是等待任意一个子进程退出。

所以如果我们要等待多个进程退出,我们要这样做

如下代码所示

#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>#define N 10    void RunChild()    
{    int cnt = 5;    while(cnt)    {    printf("I am a child process, pid:%d, ppid:%d\n",getpid(),getppid());    sleep(1);    cnt--;    }    
}    
int main()    
{    for(int i = 0; i < N; i++)    {    pid_t id = fork();    if(id == 0)    {    RunChild();    exit(0);    }    printf("create child process: %d success\n",id);    }    sleep(10);    for(int i = 0; i < N; i++)    {    pid_t id = wait(NULL);    if(id > 0)    {    printf("wait %d success\n",id);                                                  }    }    sleep(5);return 0;    
}

运行结果如下

image-20231116162422040

image-20231116162355469

上面都是子进程会退出的情况,那么如果子进程都不退出呢?

即我们将上面的子进程都改为死循环

那么最终的运行结果是一个也不退出,父进程也不退出,在那里阻塞等待。

image-20231116163402097

所以这说明,如果子进程不退出,父进程默认在wait的时候,调用这个系统调用的时候,也就不返回,默认就叫做阻塞状态!

所以说阻塞不仅仅只是像我们之前要等待scanf的时候,需要等待硬件,还有可能等待软件

3.waitpid

现在我们已经知道对于子进程的僵尸进程问题是如何解决的了,就是使用wait即可。这解决了进程等待中最为重要的一点,那么如果父进程需要获得子进程的退出时的状态,得知子进程任务完成的怎么样了,那么应该如何解决呢?

pid_ t waitpid(pid_t pid, int *status, int options);

返回值:

  • 当正常返回的时候waitpid返回收集到的子进程的进程ID;
  • 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
  • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

参数:

  • pid:

Pid=-1,等待任一个子进程。与wait等效。

Pid>0.等待其进程ID与pid相等的子进程。

  • status:

WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)

WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

  • options:
    WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

从这里的描述,我们就可以知道,wait其实就相当于waitpid的一个子集

即在这段代码中,wait与waitpid是等价的

image-20231116170410856

image-20231116170457366

在我们这个wait和waitpid的函数中,他们都有一个status,这个就是用来获取退出时的状态的。如果我们不想用,直接设置为空即可,这两个函数的这个参数是一样的

image-20231116170713686

对于这个status,它是一个输出型参数。其次,这个int是被当作几部分来使用的。

我们可以先来使用一下,为了展示出效果,我们让子进程的退出设置为1

image-20231116172438286

最终运行结果为

image-20231116172521576

我们可以发现,我们退出信息本应该是1,但是结果却是256

这里我们就有如下几个问题

  1. 子进程退出,一共会有几种退出场景呢?

对于这个问题,我们在前面已经知道了:总共三种场景

①:代码运行完毕,结果正确 ②:代码运行完毕,结果不正确 ③:代码异常终止

  1. 父进程等待,期望获得子进程退出的哪些信息呢?

①:子进程代码是否异常?②:没有异常的话,结果对吗?这里可以用exitcode退出码来获取,不对是什么原因呢?可以用不同的退出码表示原因

所以我们可以看到,这个status需要做的事情其实挺多的,所以这个变量注定了不能被看作一个,而是要划分为几个部分

image-20231116173351873

对于这个status它一共有32位,但是我们目前只考虑它的低16位

低八位,用于表示是否出异常了。其中有一共比特位是core dump标志位,我们后面再谈

我们在前面说过,一共进程异常了,本质就是该进程收到了一个信号。

对于它的次低八位,代表的就是退出的状态,即退出码

image-20231116180710223

比如我们刚刚所说的明明是退出码是1,但是打印结果是256,其实就是退出码的最低位被置为了1

image-20231116180806236

对于第七位,我么可以看到总共有64种信号,但是我们会发现没有0号信号,是因为0要代表正常终止。所以总共65种状态,就需要七位来表示。

image-20231116180943671

那么在这里我们可能会有一个问题,就是我们觉得可能没有必要这样做,因为完全可以设置一个全局变量,然后再子进程退出的时候,修改这个全局变量来处理状态就可以了,不需要用wait来处理?

其实这是因为,进程具有独立性

即便子进程中将这个全局变量给修改了,但是父进程也是看不到的。所以必须得通过wait系统调用,让操作系统去拿这个数据。

我们可以这样做,就可以分别拿出两种码了

image-20231116183217652

运行结果为

image-20231116183253354

如果我们的退出码是11

image-20231116183415758

那么运行结果的退出码就是11

image-20231116183449280

我们也可以模拟当他出现异常的时候

image-20231116183853947

可以看到,子进程第一次就发生了除0错误,直接进入了僵尸状态。

并且最终就是8号信号浮点数错误

image-20231116184002668

我们也可以让他进入死循环,然后我们使用kill去杀掉这个信号

image-20231116184951852

4.status的原理

在下图种,意思是,父进程再某行种调用了waitpid这个函数,cpu去调度父进程,当子进程执行完毕的时候。子进程为了保证自己的结果可以被上层获取,子进程可以允许把代码和数据释放掉,但是子进程对应的进程控制块不能被释放掉。我们需要将子进程退出时的信息放入进程控制块中

image-20231116185916779

所以子进程中一定有sigcode,exitcode

如下在linux内核中就可以看到这两个

image-20231116190251450

当他退出时,会将值写入exit_code中,当他异常终止时,会将信号写入exit_signal中。然后waitpid就可以读取这里面的数据了

waitpid一方面可以检测当前进程是否退出了,比如说z状态。是z状态,就直接读取这两个值,通过位运算,让上层拿到

image-20231116190447768

所以waitpid的核心工作就是读取子进程的task_struct,内核数据结构对象,并且将进程的Z状态改为X状态

那么为什么不让我们写代码时候直接访问子进程的pcb中呢,而是必须要通过系统调用呢?

我们必须要通过管理者拿到被管理者的数据,不能直接拿被管理者的数据,因为操作系统不相信任何人

5.等待失败

前面我们知道,wait/waitpid函数在等待失败的时候,会返回-1,那么什么时候会失败呢?

这里有一个很经典的场景,那就是等待的进程不存在或者等待的进程不是该进程的子进程

image-20231116201118094

image-20231116201149355

这就说明了,等待的进程必须是该进程的子进程

6.与status有关的两个宏

其实在我们的代码中,如果要让我们去写这两个的话是比较麻烦的

image-20231116201653437

所以linux提供了两个宏

  • WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
  • WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

我们可以这样用

image-20231116202146635

image-20231116202211322

我们可以在试一下多进程的

image-20231116203830467

运行结果为

image-20231116203932399

不过要注意的是,我们这个具有一定的偶然性,因为多进程的话不一定越早的就一定是最快的。取决于CPU的调度


所以现在我们就知道了前面所说的进程等待的原因之二了:我们要通过进程等待,获得子进程的退出情况,要能够知道布置给子进程的任务,它完成的怎么样了----要么关心,也可能不关心。我们也就知道了main函数的返回到底是什么了

就好比,当我们正在运行我们上面的代码的时候,bash也在等待这个进程退出。因为这个进程也是bash的子进程。

7.options

我们知道,wait本身会等待子进程,但是当子进程一直停不下来的时候,父进程只能等待,wait会导致父进程阻塞调用,导致父进程陷入阻塞状态。options可以指定等待方式。如果是0,则是阻塞等待方式(即父进程要等待子进程,子进程一直处于R,父进程就一直处于S,然后将父进程投入到等待队列中,投入到了子进程的等待队列。当子进程结束的时候,再从这个等待队列中将父进程唤醒)

我们在等待的时候,还可以选择另外一种等待方式:非阻塞轮询

pid_ t waitpid(pid_t pid, int *status, int options);

在这个函数中,如果options是0,那么就是阻塞等待方式

还有一种选项是WNOHANG (wait no hang, hang可以理解为夯住了,类似于服务器宕机了,意思是等待的时候不要夯住,也就是非阻塞)

类似于小明有事找小王

如果小明在楼下给小王隔一会就打一下电话,因为小王一直说忙着等会马上到。这就是非阻塞轮询(小明是在不打电话的时候是非阻塞的,而且是一直循环的打电话)

如果小明在楼下给小王打电话,然后不挂了,知道小明事情做完才挂电话,这就是阻塞等待(因为小明啥也干不了了)

如果小明在楼下给小王隔一会就打一下电话,在这中间的间隙做一些自己的事情,就是非阻塞轮询+做自己的事情

与之对应的就是pid_t的返回值其实应该有三种

大于0:等待成功,即返回子进程的pid

小于0:等待失败

等于0:等待的条件还没有就绪。

如下就是非阻塞轮询的示例

image-20231116212255400

运行结果如下所示:

注意在非阻塞轮询中,最好加上sleep,否则的话频繁的printf,可能会对系统压力比较大,导致卡住了。达不到预期的结果。


那么这里我们可能会疑惑父进程具体要具体做什么样子的工作?

我们可以用下面这个例子

#include<stdio.h>    
#include<unistd.h>    
#include<stdlib.h>    
#include<sys/types.h>    
#include<sys/wait.h>    #define TASK_NUM 10    
typedef void(*task_t)();    
task_t tasks[TASK_NUM];    
void task1()    
{    printf("这是一个执行打印日志的任务,pid:%d\n",getpid());    
}    
void task2()    
{    printf("这是一个执行检测网络健康状况的任务,pid:%d\n",getpid());    
}    
void task3()    
{    printf("这是一个绘制图形界面的任务,pid:%d\n",getpid());    
}    
void InitTask()    
{    for(int i = 0; i < TASK_NUM; i++) tasks[i] = NULL;    
}    
int AddTask(task_t t)    
{    int pos = 0;    for(pos = 0; pos < TASK_NUM; pos++)    {    if(!tasks[pos]) break;    }    if(pos == TASK_NUM) return -1;                                                                                                             tasks[pos] = t;    return 0;    
}    
void DelTask()                                     
{}
void CheckTask()
{}
void UpdateTask()
{}
void ExecuteTask()
{for(int i = 0; i < TASK_NUM; i++){if(!tasks[i]) continue;tasks[i]();}
}
int main()
{pid_t id = fork();if(id < 0)                                     {perror("fork:");return 1;}else if(id == 0){int cnt = 5;while(cnt){printf("I am child,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);cnt--;sleep(1);}exit(11);}else{int status = 0;InitTask();AddTask(task1);AddTask(task2);AddTask(task3);while(1) //轮询{pid_t ret = waitpid(-1, &status,WNOHANG); //非阻塞if(ret > 0){if(WIFEXITED(status)){printf("进程是正常跑完的,退出码为:%d\n",WEXITSTATUS(status));}else {printf("进程出异常了\n");}break;}else if(ret < 0){printf("wait fail\n");break;}else {ExecuteTask();usleep(500000);//printf("子进程还没有退出,在等等\n");//sleep(1);}}sleep(3);}return 0;
}

运行结果为

image-20231117202347179

在这里我们需要注意的是,在这里,我们等待子进程才是最核心的任务,这些其他的任务都是一些顺带的事情。

这些顺带的任务不可以太重了,应该得是轻量化的任务。比如打印日志,检测网络状况等。而且在这里,我们这里也只是延迟回收了一会子进程,而不是说不回收子进程了。

在上面的代码中,我们只是单进程的等待任务,如果我们想要改为多进程的等待任务的话,那么我们只需要将这里稍作修改即可,不让他直接break,而是设置一个计数器,计数子进程的个数,当一个子进程结束后,计数器减减即可。只有减到0以后,才是break。还有就是在出错的时候,也break即可

image-20231117203625030

最后一点需要注意的是

waitpid在回收子进程的时候,它可以保证父进程一定是最后一个被回收的。因为子进程可以全部被waitpid给回收掉。

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

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

相关文章

034、test

之——全纪录 目录 之——全纪录 杂谈 正文 1.下载处理数据 2.数据集概览 3.构建自定义dataset 4.初始化网络 5.训练 杂谈 综合方法试一下。 leaves 1.下载处理数据 从官网下载数据集&#xff1a;Classify Leaves | Kaggle 解压后有一个图片集&#xff0c;一个提交示…

Linux :远程访问的 16 个最佳工具(一)

通过远程桌面协议 (RDP) 可以访问远程 Linux 桌面计算机&#xff0c;这是 Microsoft 开发的专有协议。它为用户提供了一个图形界面&#xff0c;可以通过网络连接连接到另一台/远程计算机。 FreeRDP 是 RDP 的免费实现。 RDP以客户端/服务器模型工作&#xff0c;其中远程计算机必…

服务器集群配置LDAP统一认证高可用集群(配置tsl安全链接)-centos9stream-openldap2.6.2

写在前面 因之前集群为centos6&#xff0c;已经很久没升级了&#xff0c;所以这次配置统一用户认证也是伴随系统升级到centos9时一起做的配套升级。新版的openldap配置大致与老版本比较相似&#xff0c;但有些地方配置还是有变化&#xff0c;另外&#xff0c;铺天盖地的帮助文…

R语言绘制精美图形 | 火山图 | 学习笔记

一边学习&#xff0c;一边总结&#xff0c;一边分享&#xff01; 教程图形 前言 最近的事情较多&#xff0c;教程更新实在是跟不上&#xff0c;主要原因是自己没有太多时间来学习和整理相关的内容。一般在下半年基本都是非常忙&#xff0c;所有一个人的精力和时间有限&#x…

「Verilog学习笔记」使用8线-3线优先编码器Ⅰ实现16线-4线优先编码器

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 分析 当EI10时、U1禁止编码&#xff0c;其输出端Y为000&#xff0c;GS1、EO1均为0。同时EO1使EI00&#xff0c;U0也禁止编码&#xff0c;其输出端及GS0、EO0均为0。由电路…

Postman内置动态参数以及自定义的动态参数

近期在复习Postman的基础知识&#xff0c;在小破站上跟着百里老师系统复习了一遍&#xff0c;也做了一些笔记&#xff0c;希望可以给大家一点点启发。 一&#xff09;内置动态参数 {{$timestamp}} 生成当前时间的时间戳{{$randomInt}} 生成0-1000之间的随机数{{$guid}} 生成随…

Ansys Electronics Desktop仿真——HFSS线圈寄生电阻,电感

利用ANSYS Electronics Desktop&#xff0c;可在综合全面、易于使用的设计平台中集成严格的电磁场分析和系统电路仿真。按需求解器技术让您能集成电磁场仿真器和电路及系统级仿真&#xff0c;以探索完整的系统性能。 HFSS&#xff08;High Frequency Structure Simulator&#…

matplotlib 绘制双纵坐标轴图像

效果图&#xff1a; 代码&#xff1a; 由于使用了两组y axis&#xff0c;如果直接使用ax.legend绘制图例&#xff0c;会得到两个图例。而下面的代码将两个图例合并显示。 import matplotlib.pyplot as plt import numpy as npdata np.random.randint(low0,high5,size(3,4)) …

串口通信原理及应用

Content 1. 前言介绍2. 连接方式3. 数据帧格式4. 代码编写 1. 前言介绍 串口通信是一种设备间非常常用的串行接口&#xff0c;以比特位的形式发送或接收数据&#xff0c;由于成本很低&#xff0c;容易使用&#xff0c;工程师经常使用这种方式来调试 MCU。 串口通信应用广泛&a…

设计模式-备忘录模式-笔记

动机&#xff08;Motivation&#xff09; 在软件构建过程中&#xff0c;某些对象的状态在转换过程中&#xff0c;可能由于某种需要&#xff0c;要求程序能够回溯到对象之前处于某个点时的状态。如果使用一些公有接口来让其他对象得到对象的状态&#xff0c;便会暴露对象的细节…

【AI视野·今日Robot 机器人论文速览 第六十五期】Mon, 30 Oct 2023

AI视野今日CS.Robotics 机器人学论文速览 Mon, 30 Oct 2023 Totally 18 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Robotics Papers Gen2Sim: Scaling up Robot Learning in Simulation with Generative Models Authors Pushkal Katara, Zhou Xian, Katerina F…

在listener.ora配置文件中配置listener 1527的监听并且使用tnsnames连接测试

文章目录 前言&#xff1a;一、命令语句实现1、监听介绍2、编辑 listener.ora 文件&#xff1a;寻找配置文件对配置文件进行配置 3、重启监听4、配置TNS 二、图形化界面实现1、listener.ora文件配置2、tnsnames.ora文件配置 三、测试连接 前言&#xff1a; 命令实现和图形化实…

Mysql数据库 16.SQL语言 数据库事务

一、数据库事务 数据库事务介绍——要么全部成功要么全部失败 我们把完成特定的业务的多个数据库DML操作步骤称之为一个事务 事务——就是完成同一个业务的多个DML操作 例&#xff1a; 数据库事务四大特性 原子性&#xff08;A&#xff09;&#xff1a;一个事务中的多个D…

基于JavaWeb+SSM+购物系统微信小程序的设计和实现

基于JavaWebSSM购物系统微信小程序的设计和实现 源码获取入口前言主要技术系统设计功能截图Lun文目录订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码获取入口 前言 第一章 绪 论 1.1选题背景 互联网是人类的基本需求&#xff0c;特别是在现代社会&#xff0c;…

亲测一款超实用的在线制作产品册工具,一看就会

最近&#xff0c;我一直在寻找一款简单易用的在线制作产品册工具&#xff0c;终于让我找到了一个超实用的神器&#xff01;这款工具不仅功能强大&#xff0c;而且操作简单&#xff0c;一看就会。 首先&#xff0c;这款工具提供了丰富的模板和素材&#xff0c;用户可以根据自己的…

自动驾驶汽车:人工智能最具挑战性的任务

据说&#xff0c;自动驾驶汽车是汽车行业梦寐以求的状态&#xff0c;将彻底改变交通运输业。就在几年前&#xff0c;对自动驾驶汽车的炒作风靡一时&#xff0c;那么到底发生了什么呢&#xff1f;这么多公司吹嘘到2021年我们将迎来的无人驾驶汽车革命在何处&#xff1f;事实证明…

基于深度学习的活体人脸识别检测算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1. 活体人脸识别检测算法概述 4.2. 深度学习在活体人脸识别检测中的应用 4.3. 算法流程 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022a 3.部分核心程序 …

【论文阅读】A Survey on Video Diffusion Models

视频扩散模型&#xff08;Video Diffusion Model&#xff09;最新综述GitHub 论文汇总-A Survey on Video Diffusion Models。 paper&#xff1a;[2310.10647] A Survey on Video Diffusion Models (arxiv.org) 0. Abstract 本文介绍了AIGC时代视频扩散模型的全面回顾。简要介…

C++网络编程库编写自动爬虫程序

首先&#xff0c;我们需要使用 C 的网络编程库来编写这个爬虫程序。以下是一个简单的示例&#xff1a; #include <iostream> #include <string> #include <curl/curl.h> #include <openssl/ssl.h>const char* proxy_host "duoip"; const in…

.NET8.0 AOT 经验分享 FreeSql/FreeRedis/FreeScheduler 均已通过测试

2023年11月15日&#xff0c;对.net的开发圈是一个重大的日子&#xff0c;.net 8.0正式版发布。 圈内已经预热了有半个月有余&#xff0c;性能不断超越&#xff0c;开发体验越来越完美&#xff0c;早在.net 5.0的时候就各种吹风Aot编译&#xff0c;直到6.0 7.0使用仍然比较麻烦…