【Linux】线程预备知识{远程拷贝/重入函数与volatile关键字/认识SIGCHILD信号/普通信号/实时信号}

news2024/11/13 7:55:16

文章目录

  • 0.远程拷贝
  • 1.重入函数与volatile关键字
  • 2.认识SIGCHILD信号
  • 3.普通信号/实时信号

0.远程拷贝

  1. 打包资源:tar czf code.tgz *
  2. 远程传输:scp code.tgz usr@服务器ip:/home/usr/路径
  3. 解压:tar xzf code.tgz

1.重入函数与volatile关键字

先看一个现象

在这里插入图片描述
main函数调用insert函数向一个链表中插入节点node1,插入操作分为两步,刚做完第一步时,因为某种原因(如硬件中断)使进程切换到内核态,从内核态返回用户态之前检查到有信号待处理,于是切换到用户态执行sighandler函数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的两步都做完之后从用户态返回内核态,此时信号处理完毕,从内核态返回用户态就从main函数调用的insert函数中继续往下执行,先前做第一步之后被打断,现在继续做完第二步。⇒ main函数和sighandler先后 向链表中插入两个节点,而最后只有一个节点真正插入链表中了。而node2的地址无法得知 ⇒ 内存泄漏
insert函数被不同的执行流调用,有可能在第一次调用还没返回时就再次进入该函数 ⇒ 函数的重入。insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为不可重入函数。反之,如果一个函数在被重入执行的过程中不会出现任何错误,则别称为可重入函数。

什么样的函数为不可重入函数

不可重入函数是指在函数执行过程中会使用全局变量或静态变量的函数。这些函数没有保护它们的共享资源,因此在多个线程或中断同时调用时,可能会产生不可预测的结果。它们不能运行在多任务环境下,除非能保证互斥。

具体来说,满足以下任一条件的函数多数为不可重入函数:

函数体内使用了静态的数据结构。
函数体内调用了malloc()或free()函数。
函数体内调用了标准I/O函数,因为标准I/O库很多实现都以不可重入的方式使用全局数据结构。
函数体内访问了全局变量。
函数进行了浮点运算,在许多的处理器/编译器中,浮点一般都是不可重入的。
另外,一些常见的函数如printf(),由于内部包含了semTake操作,而在中断服务程序中不能有阻塞操作,因此也被视为不可重入函数。

请注意,不可重入函数在实时系统设计中被视为不安全函数,因为它们在多线程或中断环境下的行为可能会导致数据错误或不可预料的后果。因此,在编写多线程或中断驱动的代码时,应尽量避免使用不可重入函数,或采取适当的互斥措施来保护共享资源。

可重入函数怎么编写

编写可重入函数(也称为可重入代码或线程安全函数)是确保在多线程环境下函数能够安全地被并发调用而不产生数据竞争或不可预测行为的关键。以下是一些编写可重入函数的基本指导原则:

避免使用全局变量和静态变量:
全局变量和静态变量在多线程环境中是共享的,因此如果不加以保护,就可能导致数据竞争。尽量避免在函数内部使用它们。
如果必须使用全局或静态变量,确保使用互斥锁(如互斥量、信号量或读写锁)来保护对这些变量的访问。
使用局部变量:
局部变量是线程私有的,因此不会引起数据竞争。尽可能地使用局部变量来存储函数执行过程中的中间结果。
不要调用不可重入的函数:
确保你的函数不调用任何不可重入的函数,例如标准库中的某些I/O函数或其他可能使用全局状态的函数。
避免使用某些库函数:
一些库函数,尤其是那些涉及I/O操作或静态内存分配的函数,可能不是线程安全的。查阅相关文档以确定哪些函数是线程安全的,并避免使用那些不是线程安全的函数。
使用线程局部存储(Thread-Local Storage, TLS):
如果确实需要某种形式的持久性状态,并且这个状态需要在多个函数调用之间保持,可以考虑使用线程局部存储。这样,每个线程都会拥有自己独立的变量副本,从而避免了数据竞争。
使用原子操作:
对于简单的、不涉及复杂数据结构或大量计算的共享变量访问,可以使用原子操作来确保操作的原子性。原子操作通常提供对单个变量或内存位置的不可分割的访问。
确保函数的可重入性经过测试:
编写单元测试和多线程测试来验证函数的可重入性。确保在不同线程中同时调用该函数时不会产生错误的结果或行为。
下面是一个简单的可重入函数示例,它使用局部变量来避免数据竞争:

c
#include <stdio.h>  
#include <stdlib.h>  
  
// 假设这是一个线程安全的锁函数  
void lock(void);  
  
// 假设这是一个线程安全的解锁函数  
void unlock(void);  
  
// 可重入函数示例  
int safe_increment(int *counter) {  
    int local_copy;  
    lock(); // 获取锁以保护对counter的访问  
    local_copy = *counter;  
    local_copy++;  
    *counter = local_copy;  
    unlock(); // 释放锁  
    return local_copy;  
}

在这个示例中,safe_increment 函数使用了一个局部变量 local_copy 来存储 counter 的当前值,对其进行递增操作,然后再将结果写回 counter。在访问 counter 的过程中,使用了 lock 和 unlock 函数来保护对它的访问,确保同一时间只有一个线程能够修改 counter 的值。

请注意,上述示例中的 lock 和 unlock 函数需要是线程安全的,并且在实际应用中可能需要使用具体的同步原语(如互斥量)来实现。此外,具体的同步机制的选择和实现将取决于你所使用的编程语言和线程库。

可重入函数和不可重入函数的优缺点

可重入函数和不可重入函数在多任务环境中各有其优缺点。他们是是函数的一种特征,目前我们用的90%函数,都是不可重入的。

可重入函数的优点主要体现在:

灵活性:可重入函数通过在函数内部调用自身,可以提高程序实现的灵活性,实现多种重叠功能,如大量的并发任务处理、fork/join模式或服务器/客户端系统。
稳定性:可重入函数使得程序更加稳定,因为它可以用于错误控制。当发生错误时,可以直接把调用过程重复执行,实现异常情况的处理。
代码重用性:可重入函数能够大大减少函数的代码量,使代码更加紧凑,更易于理解和维护,提高了代码的可重用性。
运行效率:在一些特定情况下,如逐步累加的操作中,可重入函数的使用可以提高编译器的优化效果,从而提高程序的运行效率。
然而,不可重入函数在某些情况下也有其使用的价值,但它们的缺点也很明显:

安全性问题:不可重入函数由于使用了未受保护的系统资源,如全局变量区或中断向量表,因此在多任务环境下可能会引发数据竞争和不可预测的行为,这被视为不安全函数。
限制使用场景:不可重入函数不能运行在多任务环境中,除非能保证互斥,这限制了它们的使用范围。
综上所述,可重入函数和不可重入函数各有其适用场景和优缺点。在选择使用哪种函数时,需要根据具体的程序需求、运行环境以及资源使用情况进行综合考虑。

优化/不优化的代码运行结果

编译器有时候会自动的给我们进行代码优化;一些程序员写Makefile的时候会添加选项让编译时进行优化;
在这里插入图片描述

不加优化选项 -fpermissive是为了类型抓换不让报错(我们就想这么操作)这个选项在本文无用,这个makefile是上篇文章讲信号时用的

在这里插入图片描述

加优化选项

在这里插入图片描述

优化后程序变大了

在这里插入图片描述

对flag进行volatile修饰后

volatile ⇒ 保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量
的任何操作,都必须在真实的内存中进行操作
在这里插入图片描述

在这里插入图片描述

对上述场景的认识和解读

编译器在编译过程中监测到变量flag在主控制流main的整个执行过程中都未发生变化【主观臆断】,所以将其优化为寄存器变量。导致while语句的每次条件判断都是直接取寄存器中的值,而遮盖了flag在内存中的数据。即使进程收到2号信号后(中断处理)将flag在内存中的数值改为1,但寄存器数据被初始化为0且不发生改变,所以程序仍然保持循环,不会退出。 ⇒ 因为优化使得cpu在执行while时为了更快从寄存器读flag的值而没有从内存的实时最新值导致了错误。

“优化”的这个动作是发生在什么时候的?

发生在编译的时候。cpu只管执行和运算,不会进行代码的优化,他也没有这个能力。

volatile关键字的简述

volatile关键字在编程中,特别是Java编程中,起着非常重要的作用。volatile是Java虚拟机提供的一种轻量级的同步机制,它主要具有以下三个作用:

保证可见性:volatile关键字确保所有线程都能看到某个变量的最新值。当一个线程修改了一个被volatile修饰的共享变量时,其他线程能立即感知到这个修改,从而确保所有线程对这个变量的访问都是一致的。这有助于避免多线程环境下的数据竞争问题。
保证有序性:volatile关键字还能保证代码的执行顺序。对于volatile修饰的变量,volatile之前的代码不能调整到它的后面,volatile之后的代码也不能调整到它的前面。这有助于防止编译器优化时可能导致的指令重排问题。
禁止指令重排:volatile关键字可以禁止编译器对内存操作的指令进行重排优化,确保指令按照程序员期望的顺序执行。
需要注意的是,虽然volatile关键字提供了上述的同步机制,但它并不能保证原子性。也就是说,volatile并不能保证复合操作的原子性,例如自增操作(i++)等。因此,在需要保证原子性的场景中,还需要使用其他的同步机制,如synchronized关键字或Lock接口的实现类。

总的来说,volatile关键字是Java多线程编程中一个非常重要的工具,它可以帮助开发者在多线程环境下正确地处理共享变量的访问和修改,确保程序的正确性和稳定性。

C/CPP下volatile关键字的简述

在C和C++编程语言中,volatile关键字用于告知编译器某个变量可能会被程序无法控制的因素(如操作系统、硬件或其他并发执行的线程)改变。使用volatile关键字可以防止编译器对这些变量进行某些优化,从而确保每次从变量中读取值时都直接从内存中读取,而不是从可能被编译器优化的缓存中读取。

具体来说,volatile关键字的主要作用包括:

防止编译器优化:编译器在优化代码时,可能会将变量的值存储在寄存器中,以便更快地访问。然而,如果变量在程序执行期间可能被其他因素改变,那么从寄存器中读取的值可能就不再是最新的。使用volatile关键字可以阻止编译器进行这种优化,确保每次访问变量时都从内存中读取最新的值。

保证可见性:在多线程环境中,一个线程对volatile变量的修改对其他线程是可见的。这有助于避免由于线程间通信延迟或缓存不一致而导致的数据竞争问题。

不保证原子性:虽然volatile关键字可以确保变量的可见性,但它并不保证对变量的操作是原子的。也就是说,对于涉及多个步骤的复合操作(如自增或自减),即使使用了volatile关键字,也可能在多线程环境下出现竞态条件。在这种情况下,还需要使用其他的同步机制(如互斥锁或原子操作)来确保操作的原子性。

在C语言中,volatile是一个关键字,用于声明一个变量是“易变的”(volatile)。

当一个变量被声明为volatile时,编译器会确保对该变量的读写操作不会被优化、保持内存可见性。这是因为volatile变量的值可能会被意外地改变,例如由于硬件操作、中断处理程序或者其他线程的影响。

使用volatile关键字的主要场景包括:

并行处理:当多个线程或者进程共享一个变量时,如果这个变量可能会被其他线程或者进程改变,那么应该将这个变量声明为volatile,以确保每次读取时都是从内存中读取而不是使用缓存值。

中断处理:在中断处理程序中,通常需要访问硬件寄存器或者其他外部设备。由于这些设备的状态可能会在任何时候发生变化,因此必须将相关的变量声明为volatile,以确保每次访问都是从内存中读取最新的值。

优化禁用:有些变量的值可能会被外部因素改变,但是编译器无法检测到这种改变,因此可能会对这些变量进行优化。通过将这些变量声明为volatile,可以告诉编译器不要对这些变量进行优化。

需要注意的是,过度使用volatile关键字可能导致性能下降,因为编译器无法对某些操作进行优化。因此,在使用volatile关键字时应该谨慎,确保只在确实需要的情况下使用它。

总之,volatile关键字在C和C++编程中用于处理可能被程序无法控制的因素改变的变量,确保从变量中读取的值始终是最新的,并在多线程环境中提供可见性保证。然而,它并不保证操作的原子性,因此在需要确保原子性的情况下还需要使用其他同步机制。

2.认识SIGCHILD信号

回顾什么是僵尸进程

僵尸进程(Zombie Process)是在Unix/Linux系统中已经终止运行,但其父进程尚未对其进行善后处理(获取终止状态以及资源清理)的进程。这些进程不占用除进程表外的任何系统资源,但仍然会在进程表中占据一个位置。由于进程表有大小限制,因此如果系统中的僵尸进程过多,就可能导致无法创建新的进程。

僵尸进程的形成原因通常是因为父进程在fork子进程后,子进程先于父进程退出,而父进程没有对子进程进行wait或waitpid操作,导致子进程的进程描述符仍然留在系统中。

为了避免僵尸进程的问题,有几种解决方案:

父进程使用wait()或waitpid()系统调用来获取子进程的结束状态,并释放其占用的资源。
使用信号机制,让父进程在接收到子进程结束的信号时,调用wait()或waitpid()来处理子进程。
如果父进程不关心子进程的结束状态,可以将其设置为忽略SIGCHLD信号,这样当子进程结束时,内核会自动回收其资源,不会产生僵尸进程。
需要注意的是,僵尸进程本身并不危害系统安全,它们只是占用了进程表的位置。但是,如果系统中的僵尸进程数量过多,可能会影响到系统的性能和新进程的创建。因此,在编写涉及多进程的程序时,应妥善处理子进程的结束状态,避免产生过多的僵尸进程。

进程等待交互进程信号

进程一章讲过用wait和waitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻塞地查询是否有子进程结束等待清理(也就是轮询的方式)。采用第一种方式,父进程阻塞了就不能处理自己的工作了,采用第二种方式,父进程在处理自己的工作的同时还要记得时不时地轮询一 下,程序不是最优。
其实,子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可。
事实上,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户用sigaction函数自定义的忽略通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证在其它UNIX系统上都可用。

认识SIGCHILD信号

SIGCHLD(17)信号是在一个子进程终止或停止时由操作系统发送给其父进程的信号。它是一个通知父进程子进程状态改变的信号。父进程可以通过捕捉SIGCHLD信号,并使用相应的信号处理函数来处理子进程的状态改变。

SIGCHLD信号的处理方式:

  1. 忽略信号(默认处理)【OS级别】:SIGCHLD信号的默认处理方法就是忽略(ign)。但是如果父进程不主动去等待子进程,子进程会变为僵尸进程一直等待父进程获取其退出状态。
  2. 忽略信号(手动设置)【用户级别】:通过signal或sigaction函数手动的选择忽略SIGCHLD信号(SIG_IGN),这样系统会直接回收子进程资源,释放僵尸进程。不再需要父进程等待子进程了。通常用于父进程不关心子进程退出状态的情况。
  3. 捕捉信号:父进程通过注册一个SIGCHLD信号处理函数来捕捉SIGCHLD信号。当子进程终止或停止时,操作系统会调用该信号处理函数。
  4. 阻塞信号:父进程可以选择在某些时候阻塞SIGCHLD信号,以延迟对子进程状态改变的处理。这可以通过调用sigprocmask函数设置信号屏蔽字来实现。

测试1:忽略信号(默认处理)

忽略信号(默认处理):SIGCHLD信号的默认处理方法就是忽略(ign)。但是如果父进程不主动去等待子进程,子进程会变为僵尸进程一直等待父进程获取其退出状态。

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cassert>
using namespace std;


int main()
{
    pid_t id = fork();
    assert(id != -1);

    if (id == 0)
    {
        cout << "子进程[" << getpid() << "] 正在运行!" << endl;
        sleep(1);
        cout << "子进程[" << getpid() << "] 退出!" << endl;
        exit(0);
    }

    while (true)
    {
        cout << "父进程[" << getpid() << "] 正在运行!" << endl;
        sleep(1);
    }
    return 0;
}

在这里插入图片描述

在这里插入图片描述

测试2:忽略信号(手动设置)

忽略信号(手动设置):通过signal或sigaction函数手动的选择忽略SIGCHLD信号(SIG_IGN),这样系统会直接回收子进程资源,释放僵尸进程。不再需要父进程等待子进程了。通常用于父进程不关心子进程退出状态的情况。

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cassert>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
int main()
{
    signal(SIGCHLD, SIG_IGN);

    pid_t id = fork();
    assert(id != -1);

    if (id == 0)
    {
        cout << "子进程[" << getpid() << "] 正在运行!" << endl;
        sleep(1);
        cout << "子进程[" << getpid() << "] 退出!" << endl;
        exit(0);
    }

    while (true)
    {
        cout << "父进程[" << getpid() << "] 正在运行!" << endl;
        sleep(1);
    }

    return 0;
}

在这里插入图片描述

测试3:捕捉信号

  1. 父进程通过注册一个SIGCHLD信号处理函数来捕捉SIGCHLD信号。当子进程终止或停止时,操作系统会调用该信号处理函数。
  2. 当父进程收到SIGCHLD(17)信号时,只是知道某些子进程的状态发生了改变,不能确定:
    特别注意:如果同一时间有多个子进程退出,由于SIGCHILD的比特位只有一个,他最多只能记录一个信号的存无。所以父进程收到了SIGCHILD他并不知道有几个子进程退出,所以如果在信号处理函数中不信号等待,那么同时退出的其他几个子进程就无法获取他们的退出状态导致僵尸进程的出现。
    有多少个子进程退出;⇒ while循环等待多个进程退出,直到获取完所有退出进程的退出状态。
    哪个子进程退出了;⇒ waitpid的第一个参数传-1表示等待任意一个子进程退出。
    子进程到底是终止还是暂停 ⇒ waitpid的第三个参数传WNOHANG采用非阻塞等待,否则如果没有子进程退出,父进程将阻塞等待
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cassert>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
void handler(int signum)
{
    cout << "进程[" << getpid() << "] 捕获了一个信号(" << signum << ")" << endl;

    pid_t child_pid = 0;
    while ((child_pid = waitpid(-1, nullptr, WNOHANG)) > 0)
    {
        cout << "father: "
             << "子进程[" << child_pid << "] 退出!" << endl;
    }
}

int main()
{
    signal(SIGCHLD, handler);

    pid_t id = fork();
    assert(id != -1);

    if (id == 0)
    {
        cout << "子进程[" << getpid() << "] 正在运行!" << endl;
        sleep(1);
        cout << "子进程[" << getpid() << "] 退出!" << endl;
        exit(0);
    }

    while (true)
    {
        cout << "父进程[" << getpid() << "] 正在运行!" << endl;
        sleep(1);
    }

    return 0;
}

在这里插入图片描述

在这里插入图片描述

3.普通信号/实时信号

Linux进程间通信中的信号是一种软件中断机制,用于处理异步通信和响应突发事件。在Linux中,信号分为普通信号(也称为常规信号或标准信号)和实时信号两大类。以下是它们之间的异同点:

相同点:

命名和编号:普通信号和实时信号都遵循以SIG开头的命名规则,并且在头文件signal.h中定义为正整数常量(信号编号)。
处理机制:进程可以通过三种方式来响应信号:忽略信号、捕捉信号和执行缺省操作。无论是普通信号还是实时信号,进程对信号的处理方式都取决于传递给相应API函数的参数。
不同点:

信号范围与用途:普通信号的信号范围相对较小,主要用于系统级操作,如SIGKILL用于强制终止进程,SIGSTOP用于暂停进程等。而实时信号的信号范围有所扩大,可应用于程序自定义的目的,提供了更多的灵活性。
投递方式:普通信号的投递顺序是未定义的,且信号不排队,可能会丢失。而实时信号采取的是队列化管理,如果某一个信号多次发送给一个进程,那么该进程会多次收到这个信号,保证了信号的完整性和顺序性。
伴随数据:发送实时信号时,可以为信号指定伴随数据,供接收进程的信号处理器使用。虽然现代Linux系统也允许为标准信号指定伴随数据,但这一特性在实时信号中更为常见和强调。
优先级:对于实时信号,不同的信号传递顺序是有保障的,通常信号的编号越小,优先级越高。而普通信号的传递顺序则是未定义的,取决于具体的实现。
综上所述,普通信号和实时信号在Linux进程间通信中各有其特点和用途。普通信号主要用于系统级操作,而实时信号则提供了更大的灵活性和可靠性,特别适用于需要处理大量异步事件和保证信号顺序的场景。

为什么在linux下man 7 signal显示的信号类型中,有些信号的数字表示有多个?

Signal     Value     Action   Comment
       ──────────────────────────────────────────────────────────────────────
       SIGHUP        1       Term    Hangup detected on controlling terminal
                                     or death of controlling process
       SIGINT        2       Term    Interrupt from keyboard
       SIGQUIT       3       Core    Quit from keyboard
       SIGILL        4       Core    Illegal Instruction

       SIGABRT       6       Core    Abort signal from abort(3)
       SIGFPE        8       Core    Floating point exception
       SIGKILL       9       Term    Kill signal
       SIGSEGV      11       Core    Invalid memory reference
       SIGPIPE      13       Term    Broken pipe: write to pipe with no
                                     readers
       SIGALRM      14       Term    Timer signal from alarm(2)
       SIGTERM      15       Term    Termination signal
       SIGUSR1   30,10,16    Term    User-defined signal 1
       SIGUSR2   31,12,17    Term    User-defined signal 2
       SIGCHLD   20,17,18    Ign     Child stopped or terminated
       SIGCONT   19,18,25    Cont    Continue if stopped
       SIGSTOP   17,19,23    Stop    Stop process
       SIGTSTP   18,20,24    Stop    Stop typed at terminal
       SIGTTIN   21,21,26    Stop    Terminal input for background process
       SIGTTOU   22,22,27    Stop    Terminal output for background process

       The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.

在这里插入图片描述

在Linux下,当你使用man 7 signal命令查看信号时,可能会发现某些信号的数字表示有多个。这通常是因为不同的系统或架构可能为某些信号分配了不同的数字。尽管大部分信号的数字表示在Unix和类Unix系统(包括Linux)中是一致的,但仍然存在一些差异。

以下是一些可能导致信号数字表示有多个的原因:

架构差异:不同的硬件架构(如x86、ARM、MIPS等)可能会为某些信号分配不同的数字。这是因为不同的架构可能有不同的中断机制和处理方式,因此需要为信号分配不同的数字以适应这些差异。
历史原因:Unix和类Unix系统的发展历史悠久,不同的版本和分支可能在不同的时间点引入了新的信号或修改了现有信号的数字表示。因此,在某些系统中,某些信号可能具有不同的数字表示。
兼容性考虑:为了确保与旧版本的应用程序或系统的兼容性,某些系统可能保留了旧的信号数字表示。这样,即使在新版本的系统中引入了新的信号或修改了信号的数字表示,旧的应用程序仍然能够正常工作。
当你看到man 7 signal中某些信号有多个数字表示时,通常意味着这些数字表示对应于不同的系统或架构。在实际编程中,为了编写可移植的代码,最好使用信号的宏名称(如SIGINT、SIGTERM等)而不是直接使用数字表示。这样可以确保代码在不同的系统和架构上都能正确工作。

需要注意的是,尽管某些信号可能有多个数字表示,但它们的语义和功能通常是相同的。因此,在理解和使用这些信号时,你应该关注它们的名称和描述,而不是仅仅关注它们的数字表示。

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

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

相关文章

【智能算法】秃鹰搜索算法(BES)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2020年&#xff0c; Alsattar等人受到秃鹰猎食自然行为启发&#xff0c;提出了秃鹰搜索算法&#xff08;Bald Eagle Search&#xff0c;BES&#xff09;。 2.算法原理 2.1算法思想 BES主要分为三…

龙智亮相2024国际集成电路展览会暨研讨会(IIC Shanghai),分享芯片研发及管理解决方案与技术实践

2024年3月28-29日&#xff08;周四-周五&#xff09;&#xff0c;上海张江科学会堂&#xff0c;2024国际集成电路展览会暨研讨会&#xff08;IIC Shanghai 2024&#xff09;即将盛大开幕。龙智携芯片研发及管理解决方案、最佳实践与案例&#xff0c;以及惊喜大奖在#1A14展位等着…

第十九章 linux部署scrapyd

文章目录 1. linux部署python环境1. 部署python源文件环境2. 下载python3. 解压安装包4. 安装5. 配置环境变量6. 检查是否安装成功7. 准备python使用的包8. 安装scrapyd9. 配置scrapyd10. 开放6800端口 2. 部署gerapy1. 本机下载包2. 初始化3. 进入gerapy同步数据库4. 创建用户…

堂哥让我给他做个真人动漫头像

背景 堂哥最喜欢的动漫是死神。他给了我一张死神主角一户的头像&#xff0c;以及自己的头像&#xff0c;希望我产出一张真人动漫头像。 一户的头像&#xff1a; 堂哥自拍照&#xff1a; 最近&#xff0c;有大佬部署了个stable diffusion&#xff0c;正好拿来一试身手。 stab…

优质视频素材库排行榜前十名有哪些?

在视频创作的世界中&#xff0c;每一帧画面、每一个音符都承载着无限的可能。为了帮助你更好地探索这些可能性&#xff0c;我继续为你精选了一系列素材网站。这些网站不仅能够提供高质量的视频、音效和图像素材&#xff0c;还能激发你的创意灵感&#xff0c;助你一臂之力。 1&…

利器 | 测试必会之 Linux 三剑客 ( grep / awk / sed )

Linux 给人的印象是黑乎乎的神秘窗口&#xff0c;文本操作和数据处理似乎没有 Windows 窗口界面直观方便。其实Linux 有自己的独特的法宝&#xff0c;称之为三剑客&#xff1a;grep&#xff0c;awk 和 sed。你可以用这三件法宝很方便的处理数据 &#xff1a;查找&#xff0c;分…

时间戳的转换-unix时间戳转换为utc时间(python实现)

import datetimetimestamp = 1711358882# 将时间戳转换为UTC时间 utc_time = datetime.datetime.utcfromtimestamp(timestamp)# 格式化并输出时间 formatted_time = utc_time.strftime(%Y-%m-%d %H:%M:%S) print(formatted_time)同样:UTC如何转换为unix时间戳 from datetime …

【考研数学】如何搭配好《660》+《880》组合?

如果1800题都做不明白&#xff0c;那就不要去做880题660题 做完1800题之后&#xff0c;还迷迷糊糊&#xff0c;解题水平极低&#xff0c;都是犯了一个错误&#xff1a; 那就是为了做题而做题&#xff01; 如果这个习惯不改掉&#xff0c;那不管是做660题还是880题都起不到任…

穿越地心:3D可视化技术带你领略地球内部奇观

在广袤无垠的宇宙中&#xff0c;地球是一颗充满生机与奥秘的蓝色星球。我们每天都生活在这颗星球上&#xff0c;感受着它的温暖与恩赐&#xff0c;却往往忽略了它深邃的内部世界。 想象一下&#xff0c;你能够穿越时空&#xff0c;深入地球的核心&#xff0c;亲眼目睹那些亿万年…

ssm005基于SSM框架的购物商城系统+jsp

购物商城系统的设计与实现 摘 要 网络技术和计算机技术发展至今&#xff0c;已经拥有了深厚的理论基础&#xff0c;并在现实中进行了充分运用&#xff0c;尤其是基于计算机运行的软件更是受到各界的关注。加上现在人们已经步入信息时代&#xff0c;所以对于信息的宣传和管理就…

GL-15过流继电器 10A、5A 板前接线带附件 JOSEF约瑟

系列型号&#xff1a; GL-11过流继电器; GL-12过流继电器; GL-13过流继电器; GL-14过流继电器; GL-15过流继电器; GL-16过流继电器; GL-17过流继电器; 用途 GL-10系列过流继电器(以下简称继电器)具有反时限特性&#xff0c;应用于电机、变压器等主设备以及输配电系统的继电保…

SQLAlchemy操作数据库

数据库是一个网站的基础。 比如 MySQL 、 MongoDB 、 SQLite 、 PostgreSQL 等&#xff0c;这里我们以 MySQL为例进行讲解。 SQLAlchemy 是一个 ORM 框架 我们会以 MySQL SQLAlchemy 组合进行讲解。 在操作数据库操作之前&#xff0c;先确保你已经安装了以下两个插件&#…

spring 环境配置

1.安装idea 建议安装2020以上的版本 idea 旗舰版 下载路径 破解软件和激活码地址&#xff0c;破解软件相对麻烦&#xff0c;建议用激活码(可能不稳定)。 2.安装 JDK 版本选择1.8的 安装完以后记得配置环境变量&#xff0c;配置完以后输入java -version查看是否安装成功 参考…

Word邮件合并

Word邮件合并功能可以解决在Word中批量填写内容的需求&#xff0c;当需要大量格式相同&#xff0c;只修改少数相关内容时&#xff0c;例如利用Word制作工资条&#xff0c;通知函&#xff0c;奖状等等&#xff0c;同时操作也非常简单灵活。下面通过例子来说明邮件合并的使用方法…

技术与业务:项目成功的黄金关键

目录 前言1 明确业务需求2 技术选择与业务匹配3 解决方案设计与业务一致4 开发与实施5 持续监控与优化6 反馈循环与持续改进结语 前言 在当今数字化时代&#xff0c;技术与业务之间的紧密联系对于项目的成功至关重要。无论是开发新产品、提供服务还是改进现有流程&#xff0c;…

QT_day3:2024/3/22

作业1&#xff1a;设计界面 使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin…

【OJ】动归练习二

个人主页 &#xff1a; zxctscl 如有转载请先通知 题目 1. 91.解码方法1.1 分析1.2 代码 2. 62.不同路径2.1 分析2.2 代码 3. 63.不同路径 II3.1 分析3.2 代码 1. 91.解码方法 1.1 分析 题目所述就是把一串数字反向解码为字母映射出来&#xff0c;有多少种方法。 题目也说&…

科学计算库numpy的使用

import numpy as np import matplotlib.pyplot as plt #加载显示图片&#xff0c;目录根据实际情况写。 img_arr plt.imread(‘D:\大数据分析\python\123.jpg’) plt.imshow(img_arr) #将图片进行左右翻转 plt.imshow(img_arr[:,::-1,:]) plt.imsave(‘D:\大数据分析\pyt…

Java项目:73 ssm档案管理系统

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 角色&#xff1a;管理员、用户 用户点击进入到系统操作界面&#xff0c;可以对个人中心、警察信息管理、事故信息管理、申诉信息管理等功能模…