Linux(十) 线程,线程控制

news2025/1/20 5:12:42

目录

一、认识线程

1.1 线程是什么

1.2 为啥要有线程

并行与并发

为什么要有线程(线程的优点)

为什么线程的切换成本更低

1.3 线程的缺点

1.4 线程和进程的区别

二、线程控制

2.1 线程创建

进程ID和线程ID

2.2 线程终止

2.3 线程等待

2.4 线程分离

三、注意


一、认识线程

1.1 线程是什么

  1. 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”
  2. 一切进程至少都有一个执行线程
  3. 线程在进程内部运行,本质是在进程地址空间内运行
  4. 在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化
  5. 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流

1.2 为啥要有线程

并行与并发

首先先介绍一下并行与并发

  • 并发:指两个或多个事件在同⼀个时间段内发⽣。
  • 并⾏:指两个或多个事件在同⼀时刻发⽣(同时发⽣)。

在操作系统中,安装了多个程序,并发指的是在⼀段时间内宏观上有多个程序同时运⾏,这在单 CPU 系统中,每⼀时刻只能有⼀道程序执⾏,即微观上这些程序是分时的交替运⾏,只不过是给⼈的感觉是同时运 ⾏,那是因为分时交替运⾏的时间是⾮常短的。
⽽在多个 CPU 系统中,则这些可以并发执⾏的程序便可以分配到多个处理器上( CPU ),实现多任务并⾏ 执⾏,即利⽤每个处理器来处理⼀个可以并发执⾏的程序,这样多个程序便可以同时执⾏。⽬前电脑市场 上说的多核 CPU ,便是多核处理器,核 越多,并⾏处理的程序越多,能⼤⼤的提⾼电脑运⾏的效率。

注意:单核处理器的计算机肯定是不能并⾏的处理多个任务的,只能是多个任务在单个 CPU 上并发运 ⾏。同理 , 线程也是⼀样的,从宏观⻆度上理解线程是并⾏运⾏的,但是从微观⻆度上分析却是串⾏ 运⾏的,即⼀个线程⼀个线程的去运⾏,当系统只有⼀个 CPU 时,线程会以某种顺序执⾏多个线程, 我们把这种情况称之为线程调度。

为什么要有线程(线程的优点)

“并发编程” 成为 “刚需”.

  • 单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU 资源.

  • 有些任务场景需要 “等待 IO”, 为了让等待 IO 的时间能够去做⼀些其他的工作, 也需要用到并发编程. 

  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现,提高效率
    I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)

  • 创建一个新线程的代价要比创建一个新进程小得多

  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多

  • 线程占用的资源要比进程少很多

  • 能充分利用多处理器的可并行数量

最后, 线程虽然比进程轻量, 但是人们还不满足, 于是又有了 “线程池”(ThreadPool) 和 “协程”(Coroutine)关于线程池我们后面再介绍. 关于协程的话题我们此处暂时不做过多讨论.

为什么线程的切换成本更低

1.地址空间&&页表不需要切换,进程PCB,页表,进程地址空间的地址会保存在CPU寄存器中,切换进程要切换它们,但是其实成本并不高。
2.这条原因成本比较高,是主要原因。一条代码附近的代码有较高概率被使用,所以根据这条局部性原理,会将内存中的代码和数据预读进CPU内部,CPU内部有L1~L3 cache(缓存),对预读的内容进行保存。如果进程切换,cache就立即失效,新进程过来只能重新缓存。

1.3 线程的缺点

  • 性能损失
  • 一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
  • 健壮性降低
  • 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
  • 缺乏访问控制
  • 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
  • 编程难度提高
  • 编写与调试一个多线程程序比单线程程序困难得多

1.4 线程和进程的区别

进程是资源分配的基本单位,在用户视角里,进程 = 内核的PCB结构 + 该进程对应的代码和数据
在内核视角,承担系统资源分配的基本实体。

线程在进程内部进行,是CPU调度的基本单位。线程在进程的地址空间内运行,CPU其实不关心执行流是进程还是线程,只关心PCB(task_struct)。

进程向操作系统要资源,线程是进程给他资源。

在Linux下,PCB <= 其他OS的PCB,Linux下的进程统一称为轻量级进程,Linux没有真正意义上的线程结构,使用进程PCB模拟的线程结构,即Linux并不能给我提供线程的相关接口,只能提供轻量级进程的接口,所以为了方便使用,在用户层实现了一套用户层多进程方案,以库的方式提供给用户使用,pthread线程库--原生线程库。

线程共享进程数据,但也拥有自己的一部分数据,私有:

  • 线程ID
  • 一组寄存器
  • errno
  • 信号屏蔽字
  • 调度优先级

一组寄存器和栈是私有的体现了线程的动态属性,线程是被调度的,他就得有上下文,线程也是要被执行的,它就必须有栈来保存临时数据和出栈入栈信息

进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:

  • 文件描述符表
  • 每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
  • 当前工作目录
  • 用户id和组id
  • 代码区
  • 全局变量区
  • 堆区(可以被共享的,但一般认为是私有的)

如何看待之前学习的单进程?具有一个线程执行流的进程
 

二、线程控制

POSIX线程库

  • 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
  • 要使用这些函数库,要通过引入头文<pthread.h>
  • 链接这些线程函数库时要使用编译器命令的“-lpthread”选项
    g++ -o $@ $^ -std=c++11 -lpthread

2.1 线程创建

功能:创建一个新的线程
原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)
(void*), void *arg);
参数
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码

错误检查:

  • 传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
  • pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通过返回值返回
  • pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值判定,因为读取返回值要比读取线程内的errno变量的开销更小
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <string>
#include <pthread.h>
using namespace std;
int x = 100;
void show(const string& name)
{
    cout << name << "pid:" << getpid() << " " << x << '\n' << endl;
}

void *threadrun(void *args)
{
    const string name = (char *)args;
    while (true)
    {
        show(name);
    
        sleep(1);
    }
}

int main()
{
    pthread_t tid[5];
    char name[64];
    for (int i = 0; i < 5; i++)
    {
        snprintf(name, sizeof name, "%s-%d", "thread", i);
        pthread_create(tid + i, nullptr, threadrun, (void *)name);
        sleep(1); // 缓解传参的bug
    }
    while (true)
    {
        cout << "main thread,pid:" << getpid() << endl;
        sleep(3);
    }

    return 0;
}

进程ID和线程ID

  • 在Linux中,目前的线程实现是Native POSIX Thread Libaray,简称NPTL。在这种实现下,线程又被称为轻量级进程(Light Weighted Process),每一个用户态的线程,在内核中都对应一个调度实体,也拥有自己的进程描述符(task_struct结构体)。
  • 没有线程之前,一个进程对应内核里的一个进程描述符,对应一个进程ID。但是引入线程概念之后,情况发生了变化,一个用户进程下管辖N个用户态线程,每个线程作为一个独立的调度实体在内核态都有自己的
  • 进程描述符,进程和内核的描述符一下子就变成了1:N关系,POSIX标准又要求进程内的所有线程调用getpid函数时返回相同的进程ID,如何解决上述问题呢?
  • Linux内核引入了线程组的概念。
  • struct task_struct {
    ...
    pid_t pid;
    pid_t tgid;
    ...
    struct task_struct *group_leader;
    ...
    struct list_head thread_group;
    ...
    };
  • 多线程的进程,又被称为线程组,线程组内的每一个线程在内核之中都存在一个进程描述符(task_struct)与之对应。进程描述符结构体中的pid,表面上看对应的是进程ID,其实不然,它对应的是线程ID;进程描述符中的tgid,含义是Thread Group ID,该值对应的是用户层面的进程ID。

现在介绍的线程ID,不同于pthread_t类型的线程ID,和进程ID一样,线程ID是pid_t类型的变量,而且是用来唯一标识线程的一个整型变量。如何查看一个线程的ID呢?
 

[dgz@VM-8-2-centos lesson35]$ while true ; do ps -eLF | head -1 && ps -eLF | grep mythread | grep -v grep; sleep 1;done 
UID        PID  PPID   LWP  C NLWP    SZ   RSS PSR STIME TTY          TIME CMD
dgz       1905 31171  1905  0    6 96014  1340   0 22:23 pts/0    00:00:00 ./mythread
dgz       1905 31171  1906  0    6 96014  1340   0 22:23 pts/0    00:00:00 ./mythread
dgz       1905 31171  1912  0    6 96014  1340   1 22:23 pts/0    00:00:00 ./mythread
dgz       1905 31171  1916  0    6 96014  1340   1 22:23 pts/0    00:00:00 ./mythread
dgz       1905 31171  1921  0    6 96014  1340   0 22:23 pts/0    00:00:00 ./mythread
dgz       1905 31171  1934  0    6 96014  1340   0 22:23 pts/0    00:00:00 ./mythread

ps命令中的-L选项,会显示如下信息

  • LWP:线程ID,既gettid()系统调用的返回值。
  • NLWP:线程组内线程的个数
  • Linux提供了gettid系统调用来返回其线程ID,可是glibc并没有将该系统调用封装起来,在开放接口来共程序员使用。如果确实需要获得线程ID,可以采用如下方法: #include <sys/syscall.h> pid_t tid; tid = syscall(SYS_gettid);
  • 从上面可以看出,mythread进程的ID为31171,下面有一个线程的ID也是31171,这不是巧合。线程组内的第一个线程,在用户态被称为主线程(main thread),在内核中被称为group leader,内核在创建第一个线程时,会将线程组的ID的值设置成第一个线程的线程ID,group_leader指针则指向自身,既主线程的进程描述符。所以线程组内存在一个线程ID等于进程ID,而该线程即为线程组的主线程。
  • 至于线程组其他线程的ID则有内核负责分配,其线程组ID总是和主线程的线程组ID一致,无论是主线程直接创建线程,还是创建出来的线程再次创建线程,都是这样。
  • 强调一点,线程和进程不一样,进程有父进程的概念,但在线程组里面,所有的线程都是对等关系

同一个线程组的线程没有层次关系

  • pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
  • 前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
  • pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
  • 线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:
     pthread_t pthread_self(void);

pthread_t到底是什么类型呢?取决于实现。对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。

pthread_t tid;
typedef unsigned long int pthread_t;

怎么保证栈区是一个线程独占,不相互影响呢?
首先我们要知道OS是不知道线程存在的,所以是用户区保证的,先说结论,线程用的栈结构是共享区的栈结构,他是在pthread动态库里,因为库文件是映射到内存地址空间共享区中的,库的地址是线性的,所以为了方便线程找到自己的用户级属性,就是用tid作为线程对应属性集合的起始地址。

2.2 线程终止

如果需要只终止某个线程而不终止整个进程,可以有三种方法:

  1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
  2.  线程可以调用pthread_ exit终止自己。
  3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程

pthread_exit函数
功能:线程终止
原型
void pthread_exit(void *value_ptr);
参数
value_ptr:value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

pthread_cancel函数
功能:取消一个执行中的线程
原型
int pthread_cancel(pthread_t thread);
参数
thread:线程ID
返回值:成功返回0;失败返回错误码 

2.3 线程等待

为什么要进行线程等待?

已经退出的线程,其空间没有被释放,仍然在进程的地址空间内,如果不进行等待,会造成类似进程的僵尸问题,造成内存泄漏。创建新的线程不会复用刚才退出线程的地址空间。

功能:等待线程结束
原型
int pthread_join(pthread_t thread, void **value_ptr);
参数
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:

  • 1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
  • 2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_CANCELED。
    #define PTHREAD_CANCELED ((void *) -1)
  • 3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
  • 4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

首先我们先区别一下指针和指针变量

指针:就是地址,地址也就是指针,本质是一个数据
指针变量:本质是一个变量,一个变量它就首先需要在内存中开辟空间,空间里储存的是指针地址,就好比整数变量里储存的是整数。

那么我们知道线程的返回值是一个void类型的指针(void *),我们首先把它理解为一个地址,返回时将线程中的返回数据写入某个空间,再由主线程进行读取,整个流程为:

1.将void *类型的指针写入某个空间,于是储存空间中存放了一个地址,那么指向该空间的地址就为二级指针
2.将该储存空间的地址(二级指针)告诉主线程,主线程将其保存
3.主进程去读取该地址中存储的指针变量

所以接收返回值的变量为一个二级指针,线程的返回值就为该指针解引用,(*该指针)

2.4 线程分离

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
  • 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
int pthread_detach(pthread_t thread);

可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:

pthread_detach(pthread_self());

joinable和分离是冲突的,一个线程不能既是joinable又是分离的。

// 这些用的都是原生线程库
// 1.线程谁先运行与调度器有关
// 2.线程一旦异常,可能导致整个进程退出
// 3.线程的输入和返回值问题
// 4.线程异常退出的理解 - 不用接收返回值,因为线程出异常整个进程就都崩溃了
void* pthreadRoutine(void* args)
{
    // pthread_cancel(pthread_self); // 不推荐
    int i = 0;
    int* data = new int[10];
    while(true)
    {
        cout << "新线程:" << (char*)args << "running...." << pthread_self() << endl;
        sleep(1);
        // data[i] = i;
        // int a = 10;
        // a = a/0;
        // if(i++ == 10) break;
    }
    cout << "new thread quit\n";
    return (void*)data;
}

int main()
{
    pthread_t tid; // 本质是一个地址
    pthread_create(&tid,nullptr,pthreadRoutine,(void*)"thread 1");
    // pthread_join(tid,nullptr);// 默认阻塞等待
    printf("%lu,%p\n",tid,tid);
    int i = 0;
    while(true)
    {
        cout << "主进程:" << "running...." << endl;
        sleep(1);
        if(i++ == 5) break;
    }
    // 1.线程被取消,join的时候,退出码是-1 #define PTHREAD_CANCELED ((void *) -1)
    // PTHREAD_CANCELED
    pthread_cancel(tid);
    cout << "pthread cancel,tid:" << tid << endl;
    int* ret = nullptr;
    pthread_join(tid,(void**)&ret);

    cout << "main thread wait done ... main quit  exit code:" << (long long)ret << endl;
    // for(int i = 0;i < 10;i++)
    // {
    //     cout << ret[i] << endl;
    // }
    // 3.线程在创建并执行时,也是需要被主线程等待的,如果不进行等待,会造成类似进程的僵尸问题,造成内存泄漏
    // while(true)
    // {
    //     cout << "主进程:" << "running...." << endl;
    //     sleep(1);
    // }

    return 0;
}

三、注意

1.线程不是越多越好,因为CPU要进行线程切换,一般线程数等于CPU核数
2.man clone #fork底层调用的函数,Linux提供的轻量级进程创建函数
   man vfork # 创建和父进程共享地址空间的子进程
3.线程谁先运行与调度器有关
4.线程一旦异常整个进程都会退出
5.全局变量是所有线程共享的,要是想要每一个线程各自拥有一个同名的全局变量,该全局变量需要用__thread修饰
6.在任意线程里面创建子进程都是以主线程PCB为模板拷贝
 

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

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

相关文章

医院污水一体化处理设备有哪些

医院污水一体化处理设备通常包括以下几个主要组件&#xff1a; 预处理单元&#xff1a;用于去除污水中的固体悬浮物、颗粒物、油脂等&#xff0c;常见的预处理单元包括格栅、沉砂池、油水分离器等。生物处理单元&#xff1a;用于降解有机物质和去除氮、磷等营养物质。常见的生物…

教程:在 Apifox 中将消息通知集成到钉钉、飞书等应用

Apifox 支持将「消息通知」集成到第三方应用平台&#xff0c;包括企业微信、钉钉、飞书、Webhook 和 Jenkins。具体可在项目的【项目设置 -> 通知设置 -> 外部通知】里新建一个通知事件&#xff0c;然后在弹出的界面中配置即可。 在配置界面可以选择需要的触发事件&#…

机器学习-SVM预测

本文使用机器学习SVM对数据进行预测。仅供参考 1、数据 1.1 训练数据集&#xff1a; medol.xlsx文件示例 otv3015-1.9153622093018-1.9634097763021-1.7620284083024-1.789477583 1.2 预测数据集 test.xlsx文件示例 ot35163519 2、模型训练 train.py import pandas as …

国内常用的项目管理软件有哪些?六大企业级项目管理软件大盘点

一、 奥博思 PowerProject 项目管理软件 官方网址&#xff1a;http://www.powerproject.com.cn 北京奥博思软件技术有限公司自成立以来&#xff0c;一直专注于企业级项目管理软件的开发及解决方案&#xff0c;致力于为各类企业&#xff08;制造业、IT交付、金融、汽车及汽车零…

【工具】macOS、window11访问limux共享目录/共享磁盘,samba服务安装使用

一、samba服务安装 Samba是一个免费的开源软件实现&#xff0c;使得非Windows操作系统能够与Windows系统进行文件和打印服务共享。它实现了SMB/CIFS协议&#xff0c;并且能够在Linux、Unix、BSD等多种系统上运行。 安装 samba&#xff1a; sudo yum install samba配置 samba…

通过阿里云的PAI基于开源LLM搭建RAG检索增强对话系统

5月9日参加阿里云AI云峰会活动&#xff0c;学习了下如何借助阿里云的PAI服务进行搭建RAG检索增加对话系统。 前提&#xff1a; 为了方便省钱&#xff0c;建议先通过免费的优惠圈&#xff0c;这样就可以先不花钱学习一下啦。开始实验之前&#xff0c;请务必打开 阿里云免费试用…

VUE 滚动到指定区域scrollIntoView

背景&#xff1a;当前 VUE 页面数据量很大&#xff0c;右侧出现滚动条, 进入该页面&#xff0c;页面定位到指定区域&#xff1b; 项目要求&#xff1a; 进入页面&#xff0c;定位到指定行&#xff08;红色标记&#xff09; 直接看效果&#xff1a; 代码demo&#xff1a; <…

堆的概念及结构

目录 堆的性质&#xff1a; 堆的实现 堆向下调整算法 堆的创建 堆的插入 堆的删除 堆的应用 堆排序 对比冒泡的优势&#xff1a; 代码 头文件 源文件 如果有一个关键码的集合K { &#xff0c; &#xff0c; &#xff0c;…&#xff0c; }&#xff0c;把它的所有元…

操作系统磁盘管理类问题

例题&#xff1a;在磁盘上存储数据的排列方式会影响1/0服务的总时间。假设每个磁道被划分成10个物理块&#xff0c;每个物理块存放1个逻辑记录。逻辑记录R1,R2....R10存放在同一个磁道上&#xff0c;记录的排列顺序如下表所示&#xff1a; 假定磁盘的旋转速度为10ms/周&#xf…

问界新M5交付,「975」组合站稳中国豪华智电定位

‍作者 |老缅 编辑 |德新 5月15日&#xff0c;问界新M5已正式开启全国用户交付。从网传图片可以看到&#xff0c;华为余承东以及赛力斯AITO问界BU总裁何利扬亲自出席了首批交车仪式。 4月23日&#xff0c;在不到1个月前&#xff0c;新M5发布。新M5共推出三款车型&#xff1a; …

Pikachu 靶场敏感信息泄露通关解析

前言 Pikachu靶场是一种常见的网络安全训练平台&#xff0c;用于模拟真实世界中的网络攻击和防御场景。它提供了一系列的实验室环境&#xff0c;供安全专业人士、学生和爱好者练习和测试他们的技能。 Pikachu靶场的目的是帮助用户了解和掌握网络攻击的原理和技术&#xff0c;…

你好 GPT-4o!

你好 GPT-4o&#xff01; OpenAI公司宣布推出 GPT-4o&#xff0c;这是OpenAI的新旗舰模型&#xff0c;可以实时对音频、视觉和文本进行推理。 GPT-4o&#xff08;“o”代表“o​​mni”&#xff09;是迈向更自然的人机交互的一步——它接受文本、音频、图像和视频的任意组合作…

财富加速器!AI智能无人直播,矩阵操作引领您卖货、卖团购券、拓客,助力财富梦想实现!

财富加速器&#xff01;AI智能无人直播&#xff0c;矩阵操作引领您卖货、卖团购券、拓客&#xff0c;助力财富梦想实现&#xff01; 在当今数字化时代&#xff0c;AI智能技术正以惊人的力量催生新的商机&#xff0c;为经济增长注入源源不断的动力。如果您渴望实现财富梦想&…

go语言数组与切片

1.数组 数组 类型名是[n]elemetType&#xff0c;其中n是数组长度&#xff0c;elementType是数组元素类型。比如一个包 含2个int类型元素的数组类型可表示为[2]int。 数组一般在创建时通过字面量初始化&#xff0c;单独声明一个数组类型变量而不进行初始化是没有意义的。 packa…

Linux-CentOS-7忘记密码-修改登录密码图文详解

Linux-CentOS-7忘记密码-修改登录密码图文详解 1.重启系统&#xff1a; 在登录界面&#xff0c;选择要登录的用户并点击"Power"按钮&#xff0c;然后选择"Restart"或"Reboot"重新启动系统。 在系统启动时持续按下 “e” 键进入编辑模式。 2…

人工智能到底是什么玩意儿?

说实话&#xff0c;每次听到“人工智能”这个词&#xff0c;我都感觉像是在听天书一样。它似乎总是被包裹在一堆高大上的术语和概念里&#xff0c;让人摸不着头脑。但今天&#xff0c;我决定挑战一下自己&#xff0c;把这个问题搞个明白&#xff01; 首先&#xff0c;我得承认&…

通过gen_compile_commands.py产生compile_commands.json文件的方法

大家在使用vscode查看linux源代码时&#xff0c;会有很多飘红处&#xff0c;而且函数的跳转非常不方便。所以linux给了一个脚本gen_compile_commands.py&#xff0c;此脚本类似ctags这样&#xff0c;产生相应的关联之类的数据库&#xff0c;方便函数及文件的跳转等等。非常好。…

每日一练 2024.5.16 (补 2024.5.15)

题目&#xff1a; 给定一个 正整数 数组 beans &#xff0c;其中每个整数表示一个袋子里装的魔法豆的数目。 请你从每个袋子中 拿出 一些豆子&#xff08;也可以 不拿出&#xff09;&#xff0c;使得剩下的 非空 袋子中&#xff08;即 至少还有一颗 魔法豆的袋子&#xff09;…

Docker mysql主从同步

1. 在主节点注册一个账号&#xff0c;用于子节点访问主节点 #mysql 1主2从&#xff0c;先创建主节点 ,注意 \ 后面不要带空格 docker run --name mysql-m \ -v /usr/local/mysql/data:/var/lib/mysql \ -v /usr/local/mysql/conf:/etc/mysql/conf.d \ -v /usr/local/mysql/log:…

20231911 2023-2024-2 《网络攻防实践》实践九报告

1.实践内容 1.1 缓冲区 缓冲区是内存空间的一部分&#xff0c;在内存中预留了一定的存储空间&#xff0c;用来暂时保存输入和输出等I/O操作的一些数据&#xff0c;这些预留的空间就叫做缓冲区。 1.2 shellcode shellcode是一段用于利用软件漏洞而执行的代码&#xff0c;也可以…