【Linux 12】进程控制

文章目录

  • 🌈 Ⅰ 进程创建
    • 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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

函数式编程基本语法

文章目录 1.函数对象表现形式1.Lambda表达式&#xff08;功能全面&#xff09;1.基本语法2.只有一行逻辑&#xff0c;该逻辑结果是返回值3.复杂逻辑4.省略参数类型&#xff08;可以通过上下文推导出类型时&#xff0c;比如实现了函数式接口&#xff09;5.只有一个参数时&#x…

NAND闪存市场彻底复苏

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

在 Stable Diffusion 中控制光线的三种方式

光线在摄影中扮演着至关重要的角色&#xff0c;并对图像的整体质量和意境产生重要影响。你可以利用光线来增强主题&#xff0c;创造深度和立体感&#xff0c;传达情感&#xff0c;并突出重要细节。 在本文中&#xff0c;你将了解通过以下方法来控制光线&#xff1a; 光线提示…

基于Java的度分秒坐标转纯经纬度坐标的漂亮国基地信息管理

目录 前言 一、空间表设计 1、物理表结构 二、后台数据管理 1、数据去重 2、去重的具体实现 3、度分秒数据格式转换 4、具体的转换方法 5、新增界面的实现 三、数据管理界面 总结 前言 众所周知&#xff0c;漂亮国在全球范围内部署了大量的基地&#xff0c;用以维持其…

阿里巴巴全球数学竞赛报名条件

#竞赛概览与历史# “阿里巴巴全球数学竞赛”&#xff08;Alibaba Global Mathematics Competition&#xff09;由阿里巴巴公益、阿里巴巴达摩院共同举办&#xff0c;面向全球的数学爱好者&#xff0c;集竞赛、培训、交流于一体&#xff0c;旨在全球范围内引领开启关注数学、理解…

monitor-zabbix

监控体系理论 学习本篇文章&#xff0c;了解运维监控系统的前世今生 zabbix官网仓库地址 zabbix官网 https://www.zabbix.com/cn/zabbix官网仓库地址 http://repo.zabbix.com/zabbix/ http://repo.zabbix.com/zabbix/4.0/ubuntu/pool/main/z/zabbix-release/zabbix-release_…

数字孪生智慧机场:引领航空未来

图扑数字孪生技术赋能智慧机场&#xff0c;实现运营管理和乘客服务的全面优化。实时数据监控与智能决策助力高效安全的航空体验&#xff0c;推动行业创新与发展。

分布式理论与设计 三、分布式一致性协议

1.两阶段提交协议&#xff08;2PC&#xff09; 1&#xff09;两阶段提交协议 两阶段提交协议&#xff0c;简称2PC(2 Prepare Commit)&#xff0c;是比较常用的解决分布式事务问题的方式&#xff0c;要么所有参与进程都提交事务&#xff0c;要么都取消事务&#xff0c;即实现A…

EasyRecovery电脑数据恢复软件2024数据守护神#误删文件神器#硬盘恢复利器#数据丢失救星

&#x1f310; 你是否曾经因为误删文件、硬盘损坏等原因&#xff0c;失去了重要的数据&#xff1f;别担心&#xff0c;EasyRecovery电脑数据恢复软件是你的救星&#xff01;它能够帮你找回丢失的文件&#xff0c;让你的数据重新焕发生机。 &#x1f50d; EasyRecovery软件的核…

Enhancing CLIP with GPT-4: Harnessing Visual Descriptions as Prompts

标题&#xff1a;用GPT-4增强CLIP:利用视觉描述作为提示 源文链接&#xff1a;Maniparambil_Enhancing_CLIP_with_GPT-4_Harnessing_Visual_Descriptions_as_Prompts_ICCVW_2023_paper.pdf (thecvf.com)https://openaccess.thecvf.com/content/ICCV2023W/MMFM/papers/Manipara…

【Android面试八股文】你能说一说什么是代理模式?静态代理和动态代理分别是什么?如何实现?

文章目录 一、代理模式1.1 代理模式概念1.2 代理模式的目的1.3 代理模式的三个角色1.4 代理模式的两种实现方式1.5 代理模式的优点1.6 代理模式的缺点1.7 适用场景 二、静态代理2.1 静态代理2.2 动态代理2.2.1 JDK动态代理2.2.2 CGLIB动态代理2.2.3 JDK 动态代理和 CGLIB 动态代…

【机器学习300问】122、RNN面临哪些问题?

循环神经网络&#xff08;RNN&#xff09;主要面临梯度消失和梯度爆炸两个核心问题&#xff0c;这严重影响了其处理长期依赖的能力。此外&#xff0c;还存在一些其他的技术挑战。 一、两个主要问题 &#xff08;1&#xff09;梯度消失和梯度爆炸问题 这是RNN中最显著的问题之…

JMU 数科 数据库与数据仓库期末总结(4)实验设计题

E-R图 实体-关系图 E-R图的组成要素主要包括&#xff1a; 实体&#xff08;Entity&#xff09;&#xff1a;实体代表现实世界中可相互区别的对象或事物&#xff0c;如顾客、订单、产品等。在图中&#xff0c;实体通常用矩形表示&#xff0c;并在矩形内标注实体的名称。 属性…

大话设计模式解读03-装饰模式

本篇文章&#xff0c;来解读《大话设计模式》的第6章——装饰模式。并通过C代码实现实例代码的功能。 注&#xff1a;第3~6章讲的是设计模式中的一些原则&#xff08;第3章&#xff1a;单一职责原则&#xff1b;第4章&#xff1a;开放-封闭原则&#xff1b;第5章&#xff1a;依…

C#知识|模块化分层学习笔记

哈喽&#xff0c;你好&#xff0c;我是雷工&#xff01; 01 基本分层 典型的两层结构&#xff1a;由UI层 数据访问层 实体类构成。 其中实体类不算一层&#xff0c;本质是一个数据载体。 02 模块化分层 模块概念&#xff1a;在.NET平台中&#xff0c;模块主要是指类库项目。…

2024.6.17 作业 xyt

今日作业&#xff1a; 升级优化自己应用程序的登录界面。 要求&#xff1a; 1. qss实现 2. 需要有图层的叠加 &#xff08;QFrame&#xff09; 3. 设置纯净窗口后&#xff0c;有关闭等窗口功能。 4. 如果账号密码正确…

Modbus协议转Profibus协议模块接热传感器配置攻略

一、前言 在工业自动化控制领域&#xff0c;Modbus协议和Profibus协议是两种常见的通讯协议&#xff0c;它们在设备之间传输数据起着至关重要的作用。而Modbus协议转Profibus协议模块&#xff08;XD-MDPB100&#xff09;设备&#xff0c;则扮演着连接不同通讯协议的桥梁角色。…

新质生产力水平测算与中国经济增长新动能(dta数据及do代码)

时间跨度&#xff1a;2012-2022年 数据范围&#xff1a;全国30个省份&#xff08;不含港澳台、西藏&#xff09; 数据指标&#xff1a; 参考韩文龙等的做法&#xff0c;收集了全部控制变量与稳定性检验所需变量。 类型 符号 变量 变量定义 被解释变量 GDP 各省人均GDP…

Linux 并发与竞争基础知识学习

Linux 并发与竞争 并发与竞争 Linux 系统是个多任务操作系统&#xff0c;会存在多个任务同时访问同一片内存区域&#xff0c;这些任务可能会相互覆盖这段内存中的数据&#xff0c;造成内存数据混乱。针对这个问题必须要做处理&#xff0c;严重的话可能会导致系统崩溃。现在的…