【Linux 12】进程控制

news2024/11/27 2:40:17

文章目录

  • 🌈 Ⅰ 进程创建
    • 01. fork 函数介绍
    • 02. 写时拷贝
    • 03. fork 常规用法
    • 04. fork 调用失败的原因
  • 🌈 Ⅱ 进程终止
    • 01. 进程退出场景
    • 02. 常见退出方法
  • 🌈 Ⅲ 进程等待
    • 01. 进程等待必要性
    • 02. 进程等待的方法
      • 2.1 wait 方法
      • 2.2 waitpid 方法
    • 03. 获取子进程状态
      • 3.1 使用位运算获取退出信息
      • 3.2 使用宏获取退出信息
    • 04. 非阻塞轮询访问
  • 🌈 Ⅳ 进程程序替换
    • 01. 替换原理
    • 02. 替换函数
    • 03. 函数解释
    • 04. 命名理解
    • 05. 函数用例
      • 5.1 execl 函数使用示例
      • 5.2 execlp 函数使用示例
      • 5.3 execle 函数使用示例
      • 5.4 execv 函数使用示例
      • 5.5 execvp 函数使用示例
      • 5.6 execvpe 函数使用示例

🌈 Ⅰ 进程创建

01. fork 函数介绍

fork 函数介绍

  • 在 Linux 中可以使用 fork 函数从已经存在的进程中创建新进程
  • 新的进程为子进程,而原进程为父进程。
#include <unistd.h>

pid_t fork(void);

fork 的返回值

  • fork 函数有三种返回值
返回值状态说明
返回值 < 0表示子进程创建失败
返回值 = 0表示当前进程为子进程
返回值 > 0该返回值是子进程的 pid,当前进程为父进程,其持有子进程 pid

02. 写时拷贝

1. 什么是写时拷贝

  • 通常情况下,父子进程代码共享,父子进程在不进行写入操作时,其数据也是共享的,当任意一方试图写入时,便会以写时拷贝的方式各自留存一份副本。

在这里插入图片描述

2. 为什么写时拷贝

  1. 创建子进程时,子进程不一定要用到父进程的全部数据,因此不需要直接将父进程的所有数据全部拷贝一份给子进程,而是在要进行修改时再从父进程那拷贝这部分数据即可即可。
  2. 子进程正在尝试对这部分数据进行修改,但是父进程不打算修改这部分共享的数据,因此子进程就必须将旧数据拷贝一份进行修改。

03. fork 常规用法

父子进程执行不同的代码段

  • 判断 fork 的返回值,从而让不同的进程去执行不同的代码段。
#include <unistd.h>
#include <iostream>
#include <sys/types.h>
 
using std::cout;
using std::endl;
 
int main()
{
   	pid_t id = fork();	// 创建进程
 
	if (0 == id)    	// 执行子进程部分代码  
    	cout << "I am child process" << endl;  
    else if(id < 0) 	// 子进程的创建失败了
  	    cout << "process creation failure" << endl;
 	else            	// 执行父进程部分代码
 		cout << "I am parent process" << endl;
 		
 	return 0;                               
}

04. fork 调用失败的原因

  1. 系统中进程太多,再 fork 时就会内存不足从而导致进程创建失败。
  2. 实际用户的进程数超过了限制,系统对每个用户能创建的进程数量是有上限的。

🌈 Ⅱ 进程终止

01. 进程退出场景

  1. 代码运行完毕,结果正确。
  2. 代码运行完毕,结果错误。
  3. 代码没执行完,异常终止。

02. 常见退出方法

1. 使用 _exit 函数退出进程

  • 函数原型
    • 该函数是个系统调用。
    • _exit 函数 不支持 刷新缓冲区。
#include <unistd.h>

// 用于终止进程,status 表示进程退出时的退出码
void _exit(int status);
  • 函数用例
int main()
{
    while (true)
    {
        cout << "I am a process, pid: " << getpid() << endl;
        _exit(3);
    }

    return 0;
}

在这里插入图片描述

2. 使用 exit 函数退出进程

  • 函数原型
    • 该函数本质上是执行了系统调用的 _exit 函数。
    • exit 函数 支持 刷新缓冲区。
#include <stdlib.h>

// 用于终止进程,status 表示进程退出时的退出码
void exit(int status);
  • 函数用例
    • 在代码的任何地方调用 exit 函数都表示退出进程。
int main()
{
    while (true)
    {
        cout << "I am a process, pid: " << getpid() << endl;
        exit(2);
    }

    return 0;
}

在这里插入图片描述

3. 使用 return 退出进程

  • 执行 return n 等同于执行 exit(n),因为调用 main 的运行时函数会将 main 函数的返回值当做 exit 函数的参数。
  • return 返回的值为 0 则表示进程执行成功,反之则表示进程执行失败。且 0 之外的不同数字能够表示进程执行失败的不同原因。
#include <cstdio>
#include <cstdlib>
#include <unistd.h>

int main()
{
    return 3;
}
  • 使用 echo $? 能够查看最近一次执行的进程的退出码。

在这里插入图片描述

🌈 Ⅲ 进程等待

01. 进程等待必要性

为什么要进程等待

  • 子进程在退出时,父进程如果对其不管不问,就会造成僵尸问题,从而造成内存泄漏。
  • 进程如果进入了僵尸状态,就没人能够将其干掉,无法杀死一个已经死去的进程。
  • 父进程需要知道派发给子进程的任务完成得如何,结果是否正确,是否正常退出等。
  • 因此父进程需要通过进程等待得方式,去回收子进程所占用得资源,并且获取子进程退出的相关信息。

进程等待能做什么

  1. 父进程能够通过 wait 方法,回收子进程的资源 (必然)。
  2. 父进程能够通过 wait 方法,获取子进程的退出信息 (退出码、退出信号) (可选)。

02. 进程等待的方法

2.1 wait 方法

wait 函数原型

#include <sys/wait.h>
#include <sys/types.h>

pid_t wait (
	int *status);	// 输出型参数,能通过该参数获取子进程退出结果,默认置为空即可

wait 函数功能

  • 父进程阻塞等待任意一个子进程,子进程不退则父进程不退。
  • 该函数能够回收子进程资源,以及获取子进程的 pid。

wait 函数返回值

  • 返回值 > 0:返回值是所等待的子进程的 pid。
  • 返回值 < 0:等待失败。

函数用例

  • 演示父进程回收子进程资源
#include <iostream>
#include <sys/wait.h>
#include <sys/types.h>

using std::cout;
using std::endl;

int main()
{
    // 创建子进程
    pid_t id = fork();

    // 子进程执行自己的代码
    if (0 == id) 
    {
        for (size_t i = 0; i < 5; i++)
        {
            cout << "child process is running, pid: " 
                    << getpid() << ", ppid: " << getppid() << endl;
            sleep(1);
        }

        cout << "子进程准备退出,马上变僵尸" << endl;
        exit(0);
    }

    cout << "父进程休眠" << endl;
    sleep(8);
    cout << "父进程开始回收僵尸进程" << endl;

    // 父进程阻塞等待任意子进程
    pid_t rid = wait(nullptr);

    // 等待成功,rid 是子进程的 pid
    if (rid > 0)
        cout << "wait success, rid: " << rid << endl;

    cout << "父进程回收僵尸进程成功" << endl;
    sleep(1);

    return 0;
}

在这里插入图片描述

2.2 waitpid 方法

waitpid 函数原型

#include <sys/wait.h>
#include <sys/types.h>

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

waitpid 函数参数

  • pid_t pid
    • pid 为 -1 时,等待任意一个子进程,功能等同 wait。
    • pid > 0 时,指定具体想要等待的那个进程。
  • int *status:输出型参数,能通过该参数获取子进程退出结果,默认置为空即可
  • int options:指定父进程的等待方式,为 0 则让父进程进行阻塞等待,非 0 则进行非 阻塞等待。

waitpid 函数功能

  • 回收子进程资源,解决僵尸问题的同时,还能够获取子进程退出信息

waitpid 函数返回值

  • 返回值 > 0:返回值是所等待的子进程的 pid。
  • 返回值 < 0:等待失败。

函数用例

#include <iostream>
#include <sys/wait.h>
#include <sys/types.h>

using std::cout;
using std::endl;

int main()
{
    // 创建子进程
    pid_t id = fork();

    // 子进程
    if (0 == id)
    {
        for (size_t i = 0; i < 5; i++)
        {
            cout << "child process is running, pid: "
                 << getpid() << ", ppid: " << getppid() << endl;
            sleep(1);
        }

        cout << "子进程准备退出,马上变僵尸" << endl;
        exit(1);
    }

    cout << "父进程休眠" << endl;
    sleep(8);
    cout << "父进程开始回收僵尸进程" << endl;

    // 用来获取子进程的退出信息 (退出码 + 退出信号)
    int status;

    // 父进程阻塞等待由 id 指定的子进程
    pid_t rid = waitpid(id, &status, 0);

    // 等待成功,rid 是子进程的 pid
    if (rid > 0)
    {
        cout << "等待成功, 子进程 pid: " << rid
             << " 子进程退出码: " << status << endl;
    }

    sleep(1);

    return 0;
}

在这里插入图片描述

  • 这里子进程的退出码之所以不是 1 而是 256 的原因是变量 status 装着退出码和退出信号两部分信息。
  • 在 status 中,只有低 16 位用于存储 退出码 + 退出信号。
    • 正常退出时,这 16 位的高 8 位存储退出码,低 8 位默认全部存 0。
    • 异常退出时,这 16 位的高 8 位不使用,低 7 位 存储终止信号,还有 1 位是 core dump 标志位。
  • 当前子进程没有出异常,因此为 1 的退出码在内存中是这样存储的 0000 0001 0000 0000,因此按照整形的方式打印出的结果就是 256 了。

在这里插入图片描述

03. 获取子进程状态

3.1 使用位运算获取退出信息

  1. 获取子进程退出码:将获取的状态码右移 8 位再和 0xFF 相与即可,(status >> 8) & 0xFF
  2. 获取子进程退出信号:将获取的状态码和 0x7F 相与即可,status & 0x7F
// 1. 使用位运算获取退出信息
#include <iostream>
#include <sys/wait.h>
#include <sys/types.h>

using std::cout;
using std::endl;

int main()
{
    // 创建子进程
    pid_t id = fork();

    // 子进程
    if (0 == id)
    {
        for (size_t i = 0; i < 5; i++)
        {
            cout << "child process is running, pid: "
                 << getpid() << ", ppid: " << getppid() << endl;
            sleep(1);
        }

        cout << "子进程准备退出,马上变僵尸" << endl;
        exit(1);
    }

    cout << "父进程休眠" << endl;
    sleep(8);
    cout << "父进程开始回收僵尸进程" << endl;

    // 用来获取子进程的退出信息 (退出码 + 退出信号)
    int status;

    // 父进程阻塞等待由 id 指定的子进程
    pid_t rid = waitpid(id, &status, 0);

    // 等待成功,rid 是子进程的 pid
    if (rid > 0)
    {
        cout << "等待成功, 子进程 pid: " << rid
             << " 子进程退出信号: " << (status & 0x7F)
             << " 子进程退出码: " << ((status >> 8) & 0xFF) << endl;
    }

    sleep(1);

    return 0;
}

在这里插入图片描述

3.2 使用宏获取退出信息

  1. WIFEXITED(status):如果子进程是正常退出,则该宏的值位真。(用以查看进程是否是正常退出)
  2. WEXITSTATUS(status):如果 WIFEXITED 的值为真,则提取子进程退出码。(用以查看进程的退出码)
// 2. 使用宏获取退出信息
#include <iostream>
#include <sys/wait.h>
#include <sys/types.h>

using std::cout;
using std::endl;

int main()
{
    // 创建子进程
    pid_t id = fork();

    // 子进程
    if (0 == id)
    {
        for (size_t i = 0; i < 5; i++)
        {
            cout << "child process is running, pid: "
                 << getpid() << ", ppid: " << getppid() << endl;
            sleep(1);
        }

        cout << "子进程准备退出,马上变僵尸" << endl;
        exit(1);
    }

    cout << "父进程休眠" << endl;
    sleep(8);
    cout << "父进程开始回收僵尸进程" << endl;

    // 用来获取子进程的退出信息 (退出码 + 退出信号)
    int status;

    // 父进程阻塞等待由 id 指定的子进程
    pid_t rid = waitpid(id, &status, 0);

    // 等待成功,rid 是子进程的 pid
    if (rid > 0)
    {
    	// 子进程是正常退出的, 用户只需要关心退出码即可
        if (WIFEXITED(status)) 
        {
            cout << "等待成功, 子进程 pid: " << rid
                 << " 子进程退出码: " << WEXITSTATUS(status) << endl;
        }
    }

    sleep(1);

    return 0;
}

在这里插入图片描述

04. 非阻塞轮询访问

  • 父进程在阻塞等待子进程时,父进程这时候什么事的做不了,只能等待子进程退出才能去做自己的事,效率太低,非阻塞轮询访问就因此出现。

1. 非阻塞轮询访问

  • 父进程每隔一段时间就执行一次系统调用,判断子进程是否退出。
  • 如果子进程没有退出,则父进程子继续执行自己的任务。
  • 如果子进程已经退出,则父进程回收子进程的资源及退出信息。

2. 如何使用非阻塞等待

  • 将 waitpid 函数的第三个参数改成非 0 值即可,一般是用 WNOHANG 宏作为参数。
pid_t rid = waitpid(id, &status, WNOHANG);

3. 非阻塞轮询访问实例

// 非阻塞轮询访问
#include <iostream>
#include <sys/wait.h>
#include <sys/types.h>

using std::cout;
using std::endl;

int main()
{
    // 创建子进程
    pid_t id = fork();

    // 子进程
    if (0 == id)
    {
        for (size_t i = 0; i < 5; i++)
        {
            cout << "子进程正在运行, pid: "
                 << getpid() << ", ppid: " << getppid() << endl;
            sleep(1);
        }

        exit(1);
    }

    // 用来获取子进程的退出信息 (退出码 + 退出信号)
    int status = 0;

    while (true)
    {
        // 父进程以非阻塞状态等待子进程退出
        pid_t rid = waitpid(id, &status, WNOHANG);

        if (rid > 0)        // 等待成功, 子进程已经退出
        {
            cout << "等待成功, 子进程 pid: " << rid
                 << " 子进程退出码: " << WEXITSTATUS(status) << endl;
            break;          // 不需要再执行循环等待了
        }
        else if (0 == rid)  // 等待成功,子进程还没退出, 父进程可以执行其他任务
        {
            cout << "子进程还未退出, 父进程执行其他任务" << endl;
            // ... 父进程在等待子进程退出期间要执行的任务
        } 
        else                // 等待失败
        {
            perror("waitpid");
            break;
        }

        sleep(1);           // 父进程每隔 1 秒查询一次子进程是否退出
    }

    sleep(1);

    return 0;
}

在这里插入图片描述

🌈 Ⅳ 进程程序替换

01. 替换原理

1. 什么是程序替换

  • 在用 fork 创建子进程之后,子进程执行的是和父进程相同的程序 (但是有可能执行的是不同的代码分支),这样创建子进程就没多大意义了。
  • 如果创建的子进程想执行其他程序的代码,需要调用 exec 系列函数执行其他程序,这种操作被称之为程序替换
  • 当进程调用 exec 系列函数时,该进程的用户空间代码和数据会完成被新的程序所替换,从一个新的程序启动例程看i是执行。
  • 调用 exec 系列函数不会创建新进程,因此在调用 exec 系列函数的前后该进程的 pid 不变。

02. 替换函数

  • exec 系列函数总共有 6 种,都是以 exec 开头的,这些函数统称为 exec 函数。
#include <unistd.h>

int execl  (const char *path, const char *arg, ...);
int execlp (const char *file, const char *arg, ...);
int execle (const char *path, const char *arg, ..., char *const envp[]);
int execv  (const char *path, char *const argv[]);
int execvp (const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

03. 函数解释

1. execl 函数

int execl (					// 替换失败时返回 -1,成功时没有返回值
	const char *path, 		// 进程要执行的程序的所在路径
	const char *arg, ...);	// 参数列表,表示如何执行该程序 (命令行怎么写参数就怎么传), 以 NULL 结尾

2. execlp 函数

int execlp (
	const char *file, 		// 要执行的程序名,无需提供程序路径,会自动去 PATH 环境变量中查找
	const char *arg, ...);	// 参数列表,表示如何执行该程序 (命令行怎么写参数就怎么传)

3. execle 函数

int execle (
	const char *path, 		// 进程要执行的程序的所在路径
	const char *arg, ..., 	// 参数列表,表示如何执行该程序 (命令行怎么写参数就怎么传)
	char *const envp[]);	// 自己提供环境变量给子进程

4. execv 函数

int execv (
	const char *path, 		// 进程要执行的程序的所在路径
	char *const argv[]);	// 该参数是个指针数组,用以存储参数,表示如何执行该程序

5. execvp 函数

int execvp (
	const char *file, 		// 要执行的程序的程序名,无需提供程序路径
	char *const argv[]);	// 参数数组,表示如何执行该程序

6. execvpe 函数

int execvpe(
	const char *file, 		// 要执行的程序的程序名,无需提供程序路径
	char *const argv[], 	// 参数数组,表示如何执行该程序
	char *const envp[]);	// 自己提供环境变量给子进程

04. 命名理解

解释 exec 之外的每个字母所表示的含义

  • l (list) : 表示参数采用列表。
  • v (vector) : 表示参数用数组。
  • p (path) : 有 p 表示会自动搜索环境变量 PATH。
  • e (env) : 表示需要用户自己维护环境变量。
函数名参数格式是否带路径是否使用当前环境变量
execl列表否,需要自己提供程序路径
execlp列表
execle列表否,需要自己提供程序路径否,需要自己配置环境变量
execv数组否,需要自己提供程序路径
execvp数组
execve数组否,需要自己提供程序路径否,需要自己配置环境变量

05. 函数用例

5.1 execl 函数使用示例

  • 让当前进程以 ls -a -l 的方式执行 /usr/bin/ls 路径所指定的程序。
int main()
{
    cout << "程序替换开始" << endl;
    
    // 当前进程以 ls -a -l 的方式跑去执行 /usr/bin 目录下的 ls 程序
    execl("/usr/bin/ls", "ls", "-a", "-l", nullptr);
    
    cout << "程序替换结束" << endl;

    return 0;
}

在这里插入图片描述

5.2 execlp 函数使用示例

  • 用第一个参数作为要执行的程序的程序名,无需自己补全程序路径。
int main()
{
    pid_t id = fork();

    // 由子进程去进程程序替换
    if (0 == id)
    {
        cout << "程序替换开始" << endl;
        // 以 ls -a -l 的方式执行 ls 程序,无需自己补全程序路径
        execlp("ls", "ls", "-a", "-l", nullptr);
        cout << "程序替换结束" << endl;
        exit(1);
    }
	
	// 父进程阻塞等待子进程
    pid_t rid = waitpid(id, nullptr, 0);

    if (rid > 0)
        cout << "等待成功" << endl;

    return 0;
}

在这里插入图片描述

5.3 execle 函数使用示例

// 演示 execle 函数
int main()
{
    // 定义环境变量
    char *const env[] = { (char*)"hello=world", (char*)"PAHT=/" };
    pid_t id = fork();

    // 由子进程去进程程序替换
    if (0 == id)
    {   
        cout << "程序替换开始" << endl;
        // 以 mytest 的方式执行 当前目录下的 mytest.exe 文件
        execle("./mytest.exe", "mytest", NULL, env);
    }

    pid_t rid = waitpid(id, nullptr, 0);

    if (rid > 0)
        cout << "等待成功" << endl;

    return 0;
}

5.4 execv 函数使用示例

// 演示 execv 函数
int main()
{
    pid_t id = fork();

    // 由子进程去进程程序替换
    if (0 == id)
    {   
        // 存储参数的指针数组
        char *argv[] = { (char*)"ls", (char*)"-a", (char*)"-l" };

        cout << "程序替换开始" << endl;
        // 以 ls -a -l 的方式执行指定路径下的程序,
        execv("/usr/bin/ls", argv);
    }

    pid_t rid = waitpid(id, nullptr, 0);

    if (rid > 0)
        cout << "等待成功" << endl;

    return 0;
}

在这里插入图片描述

5.5 execvp 函数使用示例

// 演示 execvp 函数
int main()
{
	cout << "演示 execvp 函数" << endl;
    pid_t id = fork();

    // 由子进程去进程程序替换
    if (0 == id)
    {   
        // 存储参数的指针数组
        char *argv[] = { (char*)"ls", (char*)"-a", (char*)"-l" };

        cout << "程序替换开始" << endl;
        // 以 ls -a -l 的方式执行 ls 程序,无需指定程序所在路径
        execvp("ls", argv);
    }

    pid_t rid = waitpid(id, nullptr, 0);

    if (rid > 0)
        cout << "等待成功" << endl;

    return 0;
}

在这里插入图片描述

5.6 execvpe 函数使用示例

// 演示 execve 函数
int main()
{
    // 定义环境变量
    char *const env[] = { (char*)"hello=world", (char*)"PAHT=/" };
    pid_t id = fork();

    // 由子进程去进程程序替换
    if (0 == id)
    {   
        // 存储参数的指针数组
        char *argv[] = { "mytest" };

        cout << "程序替换开始" << endl;
        // 以 mytest 的方式执行 当前目录下的 mytest.exe 文件
        execve("./mytest.exe", argv, env);
    }

    pid_t rid = waitpid(id, nullptr, 0);

    if (rid > 0)
        cout << "等待成功" << endl;

    return 0;
}

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

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

相关文章

AI大模型浪潮席卷而来,你准备好乘风破浪成为行业翘楚了吗?

揭秘AI大模型浪潮&#xff1a;你准备好乘风破浪了吗&#xff1f; 在繁华的都市中&#xff0c;程序员小李一直默默耕耘在代码的海洋中。然而&#xff0c;随着AI大模型技术的迅猛发展&#xff0c;他发现自己仿佛置身于一场没有硝烟的战争中。身边的同事纷纷掌握了新技术&#xf…

AI绘图StableDiffusion最强大模型盘点 - 诸神乱战

玩了这么久的StableDiffusion&#xff0c;Civitai和HF上的各种大模型和LORA也都基本玩了个遍。 自己也一直想做一期盘点&#xff0c;选出我自己心中最好或者最有意思的那几个大模型。 毕竟每次看着模型库里几十个大模型&#xff0c;是个人都遭不住。 我在这篇文章中&#xf…

C++之模板(一)

1、为什么需要模板 将具有相同逻辑的一段代码提供一份模板&#xff0c;当我们需要处理不同类型的时候&#xff0c;可以通过数据类型当作参数来传递&#xff0c;从而实例化出对应类型的处理版本。 2、模板的定义 也是一种静态多态。 3、模板的分类 4、函数模板 5、函数模板的使…

大模型企业落地:汽车行业知识大模型应用

前言 在当今这个信息爆炸的时代&#xff0c;知识管理成为了企业提升核心竞争力的关键。特别是在汽车行业这样一个技术密集、信息量庞大的领域&#xff0c;如何高效管理和利用知识资源&#xff0c;成为了每个企业必须面对的挑战。 汽车行业的知识管理痛点 汽车行业作为现代工…

百货商场:打造品质生活

走进我们的百货商场&#xff0c;仿佛置身于一个五彩斑斓的梦幻世界。百货&#xff0c;不仅仅是购物的场所&#xff0c;更是一种品质生活的体验。 在这里&#xff0c;您可以找到最适合自己的商品选择。从家居用品到时尚服饰&#xff0c;从美食佳肴到美妆护肤&#xff0c;每一样商…

多态深度剖析

前言 继承是多态的基础&#xff0c; 如果对于继承的知识还不够了解&#xff0c; 可以去阅读上一篇文章 继承深度剖析 基本概念与定义 概念&#xff1a; 通俗来说&#xff0c;就是多种形态。具体点就是去完成某个行为&#xff0c; 当不同的对象去完成时会产生出不同的状…

数据资产入表-数据分级分类标准-数据分类

2021年9月1日&#xff0c;《中华人民共和国数据安全法》正式施行&#xff0c;明确规定“国家建立数据分类分级保护制度”&#xff0c;数据分级分类是数据安全管理的重要措施&#xff0c;它涉及到对数据资产的识别、分类和定级&#xff0c;是保障数据合规的前提。 数据分类&…

物联网主机 E6000 在智慧工地上的应用

随着科技的不断发展&#xff0c;智慧工地的概念逐渐普及。物联网技术的应用为工地管理带来了革命性的变化&#xff0c;物联网主机E6000作为一款领先的物联网主机设备&#xff0c;在智慧工地上发挥着重要作用。 物联网主机 E6000 是一种集成了多种传感器和通信技术的设备。支持融…

探秘提交任务到线程池后源码的执行流程

探秘提交任务到线程池后源码的执行流程 1、背景2、任务提交2、Worker线程获取任务执行流程3、Worker线程的退出时机1、背景 2、任务提交 线程池任务提交有两种方式,execute()和submit()。首先看一下execute方法的源码。我们发现它接收的入参是一个Runnable类型。我们按照代码…

小知识点快速总结:梯度爆炸和梯度消失的原理和解决方法

本系列文章只做简要总结&#xff0c;不详细说明原理和公式。 目录 1. 参考文章2. 反向梯度求导推导3. 具体分析3.1 梯度消失的原理3.2 梯度爆炸的原理 4. 解决方法 1. 参考文章 [1] shine-lee, "网络权重初始化方法总结&#xff08;上&#xff09;&#xff1a;梯度消失、…

NAND闪存市场彻底复苏

在全球内存市场逐渐走出阴霾、迎来复苏曙光之际&#xff0c;日本存储巨头铠侠&#xff08;Kioxia&#xff09;凭借敏锐的市场洞察力和及时的战略调整&#xff0c;成功实现了从生产紧缩到全面复苏的华丽转身。这一转变不仅彰显了企业在逆境中的生存智慧&#xff0c;也为全球半导…

C# Winform图形绘制

WinForms 应用程序中的控件是基于窗体的&#xff0c;当控件需要重绘时&#xff0c;它会向父窗体发送一个消息请求重绘。但是&#xff0c;控件本身并不直接处理绘制命令&#xff0c;所以你不能直接在控件上绘制图形。 解决方法&#xff1a; 重写控件的OnPaint方法使用CreateGr…

你能不能手敲出Spring框架?

Spring最成功的地方在于创始人Rod Johnson提出的IOC、AOP核心理念&#xff0c;反而不是其本身的技术。技术上今天可以有Spring春天&#xff0c;明天就可以有Autumn秋天。 核心理念有多重要&#xff1f;就如1871年巴黎公社的失败。公社在对抗法国zf和普鲁士占领军的背景下成立&…

[Python学习篇] Python字典

字典是一种可变的、无序的键值对&#xff08;key-value&#xff09;集合。字典在许多编程&#xff08;Java中的HashMap&#xff09;任务中非常有用&#xff0c;因为它们允许快速查找、添加和删除元素。字典使用花括号 {} 表示。字典是可变类型。 语法&#xff1a; 变量 {key1…

16.RedHat认证-Ansible自动化运维(中)

16.RedHat认证-Ansible自动化运维(中) 部署Ansible Ansible的Inventory文件 Inventory文件定义了ansible管理的主机&#xff0c;说白了就是Inventory文件中的内容是记录被管理的主机。 Inventory文件分为两种&#xff0c;一种是静态的Inventory文件&#xff0c;一种是动态的…

IAP固件升级进阶(Qt上位机)

前言 时隔近一年&#xff0c;再次接触IAP固件升级&#xff0c;这次修改了以前的一些bug&#xff0c;同时新增一些实用性的功能。 有纰漏请指出&#xff0c;转载请说明。 学习交流请发邮件 1280253714qq.com。 上位机界面 视频演示 当Up对iap固件升级的机制有了更深的理解后…

【论文阅读笔记】PA-SAM: Prompt Adapter SAM for High-Quality Image Segmentation

1.论文介绍 PA-SAM: Prompt Adapter SAM for High-Quality Image Segmentation PA-SAM&#xff1a;用于高质量图像分割的提示适配器SAM 2024年 ICME Paper Code 2.摘要 Segment Anything Model&#xff0c;SAM在各种图像分割任务中表现出了优异的性能。尽管SAM接受了超过10亿…

【Unity每日一记】如何在Unity里面添加视频,做媒体屏幕

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

降噪领夹麦克风哪个牌子好?揭秘无线领夹麦克风哪个降噪好

相信很多新手视频创作者都有一个疑问&#xff1a;为什么别人的视频或者直播音质这么清晰&#xff0c;几乎没什么噪音呢&#xff1f;其实最主要的就是麦克风的原因&#xff0c;相机或手机内置的麦克风是无法提供高质量的音频记录以及很好的指向性的。 想要拍摄出来的视频作品拥有…

零基础直接上手java跨平台桌面程序,使用javafx(七)用户操作界面探讨,这个系列结束

GUI&#xff0c;我们还是喜欢web。如果javafx有像wpf的WebView2差不多的功能&#xff0c;我们就开心了scene builder中拖出一个webview&#xff0c;然后再回到代码中。发现<?import javafx.scene.web.*?>是红色的&#xff0c;我们缺少配置。于是在pom.xml中添加JavaFX依…