【Linux线程总结】VMA ELF 地址转换 同步和互斥 条件变量 PC模型 循环队列 POSIX信号量 线程池

文章目录

  • VMA
  • ELF
  • 地址转换
  • 线程
  • 相关函数
  • 同步和互斥
  • 引入条件变量
  • 总结条件变量
  • PC模型
  • 循环队列
  • POSIX信号量
    • 接口
    • posix信号量和systemV信号量
      • 主要异同
      • 适用场景
      • 总结
    • 基于循环队列的PCModel
  • 锁--条件变量--信号量 的产生由来
  • 线程相关问题
  • 线程池
    • 回顾进程池

VMA

在这里插入图片描述

ELF

Executable and Linkable Format二进制文件格式。ELF文件可以是可执行文件、可重定位文件(如.o文件)、共享目标文件(如.so文件)或核心转储文件等。

地址转换

在这里插入图片描述

线程

  1. 进程是操作系统进行资源分配的基本单位,线程是操作系统调度(CPU执行)的基本单位。
  2. 线程是OS调度的基本单位 ⇒ CPU其实不关心执行流是进程还是线程,只关心PCB。
  3. 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  4. I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
  5. 【不同冲击下选择更好的方案,并不是线程越多越好,如果只有单一的工作,你创建了很多线程,本来单一的工作循环去做不会花费太多时间,此时由于你交给了很多线程,此时花费时间的是线程的来回切换】合理的使用多线程,能提高计算密集型程序的执行效率,能提高IO密集型程序的用户体验(例如边下边播功能,就是多线程运行的一种表现)
  6. 用户态线程的切换在用户态实现,不需要内核支持。
  7. 进程比线程安全的原因是每个进程有独立的虚拟地址空间,有自己独有的数据,具有独立性,不会数据共享这个太过宽泛与片面。
  8. 创建子线程后,主线程同样需要等待子线程退出,获取子线程退出结果,然后操作系统回收子线程PCB。线程等待不需要关心子线程是否异常,因为一旦子线程出现异常,整个进程就会随之崩溃。
  9. OS管理线程的调度和内核数据结构⇒ 内核级线程;线程库维护线程的id,栈,属性⇒ 用户级线程
  10. _ _thread是GCC编译器提供的一个关键字,它用于声明线程局部存储(Thread-Local Storage,TLS)的变量。 _ thread修饰的全局变量是每个线程都拥有其独立副本的变量。 _thread变量必须是POD(Plain Old Data)类型,即简单的数据类型,如整数、浮点数和指针,不能是带有自定义构造函数、析构函数或复杂拷贝语义的类类型.

线程共享进程的资源

代码区数据(定义一个函数,在各线程中都可以调用)
静态区数据(定义一个全局变量,在各线程中都可以访问)
堆区数据(堆空间的指针可以在各线程间传递,也可以选择私有堆空间)
共享区数据(动态库和共享内存通信)
命令行参数和环境变量

文件描述符表
每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
当前工作目录
用户id和组id

线程独有数据

线程ID(线程属性结构)
独立栈结构(线程属性结构):线程独立执行的重要依据,调用函数,开辟栈帧空间,存储临时数据
errno错误码(线程局部变量,线程属性结构)
线程上下文(一组寄存器,PCB数据):线程独立调度的重要依据
信号屏蔽字(PCB数据)
调度优先级(PCB数据)

LWP 和 pthread_t id

pthread_t:用户态标识,用于用户代码管理线程。具体的 pthread_t 实现方式依赖于系统和线程库。在 glibc 中,pthread_t 通常是线程控制块 (TCB) 的内存地址(64 位系统中为指针类型)。不同的线程可能对应不同的 pthread_t 值,即使它们共享相同的内核资源。

LWP/TID:内核态标识,用于线程调度和操作系统管理。

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/syscall.h>// g++ test.cc -lpthread -o test
void* thread_func(void* arg)
{pthread_t thread_id = pthread_self();  // 获取线程的 pthread_tpid_t tid = syscall(SYS_gettid);       // 获取线程的 TID (LWP)pid_t pid = getpid();                  // 获取进程的 PIDsleep(1);std::cout << "Thread: pthread_t = " << thread_id << ", LWP (TID) = " << tid << ", PID = " << pid << std::endl;sleep(1);return nullptr;
}int main()
{pthread_t threads[3];// 创建多个线程for (int i = 0; i < 3; ++i){pthread_create(&threads[i], nullptr, thread_func, nullptr);}sleep(3);// 主线程信息pthread_t main_thread_id = pthread_self();  // 主线程的 pthread_tpid_t tid = syscall(SYS_gettid);            // 主线程的 TIDpid_t pid = getpid();                       // 主线程的 PIDstd::cout << "Main Thread: pthread_t = " << main_thread_id << ", LWP (TID) = " << tid << ", PID = " << pid << std::endl;sleep(100);// 等待线程结束for (int i = 0; i < 3; ++i){pthread_join(threads[i], nullptr);}return 0;
}
while :; do ps -eLf | head -1 && ps -eLf | grep test;echo "==";sleep 1; done
UID          PID    PPID     LWP  C NLWP STIME TTY          TIME CMD
lhr       429788  428397  429788  0    4 23:37 pts/0    00:00:00 ./test
lhr       429788  428397  429789  0    4 23:37 pts/0    00:00:00 ./test
lhr       429788  428397  429790  0    4 23:37 pts/0    00:00:00 ./test
lhr       429788  428397  429791  0    4 23:37 pts/0    00:00:00 ./testThread: pthread_t = 140237570549504, LWP (TID) = 429789, PID = 429788
Thread: pthread_t = 140237562156800, LWP (TID) = 429790, PID = 429788
Thread: pthread_t = 140237553764096, LWP (TID) = 429791, PID = 429788
Main Thread: pthread_t = 140237570553664, LWP (TID) = 429788, PID = 429788

相关函数

fork() 创建一个新的进程,这个新进程可以执行与父进程相同的程序,也可以调用 exec() 系列函数来执行另一个程序。

vfork() 创建的子进程并不复制父进程的地址空间,而是共享父进程的地址空间。vfork() 通常用于创建一个新的进程来执行另一个程序(通过 exec() 系列函数)。由于子进程直接在父进程的地址空间中运行,因此这种方式通常比 fork() + 子进程exec() 更高效。

clone() 函数用于创建新的线程。它允许调用者指定哪些资源(如内存、文件描述符等)应该被新线程共享,哪些应该被复制。这使得 clone() 在创建轻量级线程时比 fork() 更高效。

fork() 复制父进程的所有资源;vfork() 与父进程共享地址空间;clone() 可以选择性地复制父进程的资源。

pthread_create是POSIX线程库(Pthreads)中的一个函数,clone是Linux特有的系统调用。

线程内部调用fork()

子进程只会复制调用fork()的那个线程的上下文,包括其栈、寄存器状态等。其他已经创建的线程不会被子进程继承。这意味着子进程将只拥有一个线程,即复制自调用fork()的那个线程的副本。虽然子进程只继承了一个线程,但它仍然继承了父进程的所有其他资源,如内存、文件描述符等。只有调用fork()的那个线程的上下文会被复制到子进程,其他线程不会被子进程继承。

设置为分离状态的线程发生了除零异常

设置为分离状态的线程在结束时,系统会自动回收其占用的资源,而无需主线程或其他线程进行资源的清理操作。

一个设置为分离状态的线程如果发生了除零异常,通常会导致该线程自身崩溃。由于线程是进程的执行分支,线程的崩溃可能会触发信号机制,导致整个进程终止。因此,不仅主线程会受到影响,该进程内的所有线程,包括其他非分离状态的线程,都将被终止。

设置为分离状态的线程

  1. 主线程必须最后退出,他要回收资源
  2. 如果主线程先于新线程退出,那么所有其他线程都会不正常的退出
  3. pthread_detach()的应用是有场景的:主线程创建了一个新线程让他去执行任务而不关心他的退出状态,可以让该新线程设置为分离状态,执行完后自动回收资源

同步和互斥

竞态条件

  • 竞态条件(Race Condition)是指多个线程或进程同时访问共享资源,并且对资源的访问顺序不确定,导致最终结果的正确性依赖于线程执行的具体时序。竞态条件可能导致不可预测的结果,破坏程序的正确性和一致性。

  • 竞态条件通常发生在多个线程或进程同时对共享资源进行读写操作时,其中至少一个是写操作。当多个线程或进程同时读写共享资源时,由于执行顺序的不确定性,可能会导致数据的不一致性、丢失、覆盖等问题。

  • 为了避免竞态条件的发生,可以使用同步机制来限制对共享资源的访问,确保每次只有一个线程可以访问该资源。常见的同步机制包括互斥锁、信号量、条件变量等。此外,还可以使用原子操作和线程安全的数据结构来避免竞态条件的发生。

同步和互斥

  • 互斥是为了解决安全问题,用于保护共享资源,确保在任意时刻只有一个线程可以访问该资源。互斥机制通过引入互斥锁(Mutex)来实现,当一个线程获得互斥锁时,其他线程必须等待,直到该线程释放锁才能继续访问共享资源。
  • 同步是在互斥保证了安全的前提下协调多个线程或进程的执行顺序,以确保它按照一定的顺序和规则高效地访问资源。同步机制可以通过互斥、信号量、条件变量等方式来实现,以确保线程或进程之间的有序执行。
    同步还可以包括其他机制,如条件变量、信号量等,用于实现更复杂的同步需求,例如线程间的通信和协作。

引入条件变量

只通过互斥保护共享资源不能够完全解决竞态条件问题:

  • 饥饿问题:在资源竞争的过程中,某执行流频繁的申请到资源,而导致其他执行流被长时间地阻塞或无法获得所需的资源时,就会发生饥饿问题。

  • 忙等待问题:当访问临界资源时,除了要申请互斥锁,还要检测资源是否就绪。如上一章互斥锁中多线程售票的例子,还需要检测票数是否大于0,才能进行售票。当资源不就绪时,各线程只能通过频繁的轮询申请释放锁并进行检测才能确定资源是否就绪,浪费了CPU和锁资源。这就是忙等待问题。

完全解决竞态条件问题不仅仅需要通过互斥保护共享资源,还必须通过其他同步机制进行线程间的通信和协作。

条件变量(Condition Variable)是一种线程同步机制,用于线程间的等待和唤醒操作,以实现线程间的协调和通信。条件变量通常与互斥锁(Mutex)结合使用,用于解决线程同步和竞态条件的问题。

条件变量需要与互斥锁配合使用,以保证对共享资源的访问是互斥的。对条件变量的等待和唤醒操作需要在持有互斥锁的情况下进行,以确保操作的原子性和正确性。

条件变量提供了以下两个基本操作:

等待(Wait):一个线程调用等待操作后,会释放持有的互斥锁,并进入等待状态,直到其他线程通过唤醒操作将其唤醒。在被唤醒后,线程会重新申请互斥锁,并继续执行。

唤醒(Signal):一个线程调用唤醒操作后,会选择一个或多个等待在条件变量上的线程,并将其从等待状态唤醒。被唤醒的线程会尝试获取互斥锁,并继续执行。

条件变量配合互斥锁完美的解决了忙等待和饥饿问题。

  • 条件变量的等待是一种排队阻塞等待的操作,线程会按照先进先出的顺序等待条件变量唤醒。这在一定程度上解决了多线程的饥饿问题。

  • 通过条件变量,线程可以在满足特定条件之前阻塞等待,而不是忙等待,从而提高了系统的效率和性能。同时在阻塞等待之前,它会释放所持有的互斥锁,允许其他线程获得该互斥锁并继续执行。这样,其他线程就有机会在等待期间获取互斥锁,并访问共享资源。这也在一定程度上解决了多线程的饥饿问题。

总结条件变量

  • 一个线程需要访问临界资源,如果我们不对其进行条件变量的控制,那么他就可能出现不断地访问该资源,使得其他线程无法访问且在不断地轮询检测资源的状态,这种情况不错但是不合理。引入同步的机制,主要是为了解决 访问临界资源的合理性问题即让线程按照一定的顺序,进行临界资源的访问。

  • 当一个线程想要访问临界资源前,先要检测临界资源是否处于有绪状态即是否可以被访问,而“检测”的这个行为本质也是在访问临界资源,那么对临界资源的检测也一定是需要在加锁和解锁之间。

  • 常规方式要检测条件就绪,注定了线程必须频繁申请和释放锁。有没有办法让线程检测到资源不就绪的时候,不要让线程在频繁自己检测,而是让他等待,当条件就绪的时候,通知对应的线程,让他来进行资源申请和访问。 ⇒ 条件变量!

    为了提高多线程的效率以及彻底达到多线程的目的即让多线程去高效的做任务,我们想让条件满足的时候再唤醒指定的线程 — 怎么知道条件是否满足?

    控制/生成这个资源的一方知道什么时候达到了“满足”状态。比如,生产者生产了资源,那么生产者就知道仓库里有资源,那么他就可以通知消费者来获取。又比如,消费者获取了资源,那么消费者就知道仓库里有地方可以存放资源,那么他就可以通知生产者来生产。

PC模型

生产者消费者模型的并发性:通过线程互斥使得临界资源被合法地访问(即临界资源同一时刻只能有一个线程访问),线程同步使得临界资源被合理地访问(即一个线程准备访问时检测到资源不就绪他会释放锁去等待而非轮询访问),互斥与同步使得【生产者发送数据和消费者获取数据】这一行为合理又合法,而做到这一步只是PC模型不值一提但十分重要的一个“点”,PC模型的并发体现在生产者在生产数据时,消费者可以获取数据获处理数据;消费者在处理数据时,生产者可以生产数据或发送数据。毕竟与发送和获取这样的拷贝动作来比,真正耗时的是“生产”和“处理”这两个动作。

阻塞队列原理

在队列为空时,获取元素的线程将会阻塞,直到有元素可获取;当队列已满时,尝试添加元素的线程也将阻塞,直到队列有空余空间。

在生产者消费者模型中,生产者线程负责向队列中添加数据(生产),消费者线程从队列中移除数据(消费)。使用阻塞队列可以确保当队列满时生产者线程阻塞,当队列空时消费者线程阻塞,从而避免数据的丢失或重复处理。

环形队列原理

环形队列(也称为循环队列)是一种使用固定大小数组实现的队列。当队列的尾部到达数组的末尾时,它会循环回到数组的开头。这种设计可以高效地利用数组空间,避免了传统队列在插入和删除元素时可能需要的数组移动操作。

在生产者消费者模型中,环形队列同样用于存储生产者产生的数据,供消费者线程消费。由于环形队列的空间是固定的,因此当队列满时,生产者线程需要等待消费者线程消费数据以释放空间;同样,当队列空时,消费者线程需要等待生产者线程生产数据。

循环队列

一个结构他在内存中的存储样式(存储结构/物理结构)仅仅是一块空间,这块空间能实现什么样的功能取决于设计者在软件层给这块空间维护了怎样的设计。

在这里插入图片描述

入队:queue[rear++] = x;

出队:front++;

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

队空队满条件一样 怎么判断?

通过少用一个空间 把队满的判断条件分离出去 使得空满二者判断时条件不同

在这里插入图片描述
在这里插入图片描述

1、队空:q.front == q.rear2、队满:(q.rear + 1)% N = q.front3、队长:(q.rear - q.front + N) % N4、循环计数: 
q.front = (q.front + 1) % N
q.rear=(q.rear + 1) % N

POSIX信号量

Linux下的POSIX信号量是一种实现进程/线程间通信的机制,主要用于保护共享资源,确保资源在某一时刻只被一个进程(线程)使用。它分为有名信号量和无名信号量两种类型。有名信号量的值保存在文件中,因此它可以用于线程间和进程间的同步;而无名信号量的值保存在内存中,因此它主要用于线程间的同步,如果需要用于进程间同步,则信号量需要放在共享内存中。POSIX信号量包含一个非负整型变量,并带有两个原子操作:wait(也被称为down、P或lock)和signal(也被称为up、V、unlock或post)。

接口

sem_wait P操作 value–

sem_post V操作 value++

  1. sem_init:初始化一个未命名的信号量。在内存中为信号量分配空间,并设置其初始值。这个初始值通常表示可用资源的数量或允许进入临界区的线程数。
  2. sem_destroy:销毁一个先前初始化的信号量。释放信号量所占用的内存空间,并取消其关联的任何系统资源。
  3. sem_post:增加(或“发布”)信号量的值。对信号量的值进行原子性增加操作。这通常意味着释放了一个资源或允许更多的线程进入临界区。这个操作可能会唤醒等待该信号量的一个或多个线程。
  4. sem_wait:减少(或“等待”)信号量的值。尝试对信号量的值进行原子性减少操作。如果信号量的值大于0,则将其减1并立即返回;如果信号量的值为0,则调用线程或进程将被阻塞,直到信号量的值变为正数。这个阻塞是通过系统调用和内核的调度机制实现的。

posix信号量和systemV信号量

有名信号量的值保存在文件中,因此它可以用于线程间和进程间的同步;

无名信号量的值保存在内存中,因此它主要用于线程间的同步,如果需要用于进程间同步,则信号量需要放在共享内存中。

主要异同

特性POSIX信号量System V信号量
命名可以是命名的(sem_open)或未命名的(sem_init)。仅支持未命名信号量,通过key_t标识。
创建与销毁使用sem_initsem_destroy进行初始化和销毁。使用semget创建,使用semctl删除。
跨进程可通过命名信号量实现跨进程同步。支持跨进程同步,通过信号量集实现。
操作提供sem_wait, sem_post, sem_trywait, sem_getvalue等操作。提供semop进行P/V操作,并利用semctl进行控制。
局部性通常用于线程间同步,也可以用于进程间同步。主要用于进程间同步。
API复杂性API相对简单且更容易使用。API相对复杂,使用多种类型的函数。
内存模型信号量对象通常存储在用户空间。信号量信息存储在内核中,使用IPC机制。
错误处理错误返回值直接返回。需要分析errno来获取详细错误信息。

适用场景

  • POSIX信号量:适合于多线程程序,因其提供了易于使用的接口,并且可以在多线程的应用中高效地管理资源。
  • System V信号量:由于其强大的功能,适用于需要复杂同步机制的多进程环境。

总结

选择使用POSIX信号量还是System V信号量取决于具体的需求和上下文。对于大多数现代应用,POSIX信号量由于其简洁性和易用性,通常是更受欢迎的选择。但在某些特定情况下,如需要与旧系统兼容或使用特定功能时,可以考虑System V信号量。

基于循环队列的PCModel

先加锁还是先申请信号量?

很明显,我们要先申请信号量,为什么?我们在不加锁的前提下先申请信号量时,此时是可以有多个线程调用push/pop函数的,信号量的值是大于1的,即可以有多个线程成功申请信号量,申请信号量成功的就去申请锁,申请信号量失败的就阻塞等待;而如果我们先申请锁,如果申请锁失败则阻塞,其他的线程可以申请锁;就算申请锁成功,此时已经进入了临界区,只能由成功申请锁的该线程去申请信号量,如果申请信号量成功也还好,这个线程可以继续执行,如果申请信号量失败,不仅这个申请到锁的线程在阻塞等待信号量,其他未申请到锁的线程也在当地。与先申请信号量相比:线程们都可以申请信号量,之后由某一个申请到信号量又申请到锁的去执行临近区代码;而先加锁,一旦加锁后只能有一个线程执行临界区,其余的线程只能等他执行完才能得到调度。总结:先申请信号量可以先进行信号量的申请,一旦得到调度就可以申请锁继续后续动作。

锁–条件变量–信号量 的产生由来

多线程访问共享资源==》线程安全问题~~》使用互斥锁解决安全问题

在安全的基础上存在忙等待和饥饿问题==》使用条件变量解决因不知道资源是否就绪导致的这两个问题

在安全的基础上存在忙等待和饥饿问题==》使用信号量在非临界区预定资源

总结

  1. 用条件变量配合互斥锁实现互斥与同步:申请锁 -> 判断资源是否就绪+后续访问资源 -> 释放锁。这样做的原因是我们不清楚临界资源的情况/状态
  2. 信号量提前预订了资源,通过计算机软硬件给我们提供的原子性pv接口,我们可以在非临界区知晓临界资源的情况,即,我们不再考虑资源是否就绪了!即,条件变量解决的因资源就绪而循环申请/释放锁的情况,信号量解决的是压根就不用再考虑资源是否就绪!
  3. 信号量本质是一把计数器,使得线程可以不用进入临界区就可以得知资源情况==》减少临界区内部的判断!
  4. 信号量:如果你需要控制对有限资源的访问(如线程池、限制并发用户等),信号量可能更适合。
  5. 条件变量:如果你的线程需要在某个条件满足之前等待(如生产者-消费者问题),条件变量通常是更好的选择。

可重入和线程安全

可重入函数 ⇒ 线程安全。

线程安全不一定是可重入的:如果对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数锁还未释放则会产生死锁,因此是不可重入的【示例如下,即线程只使用一个锁的死锁现象】

线程相关问题

在两个线程中同时执行f1和f2,待两个函数都返回后,a的所有可能值是哪些?

在这里插入图片描述

【待两个函数都返回后】:并不一定是两个函数都执行了,时间片用完,也是返回;
A执行1,切换,B执行34,切换,A执行2,时间片结束,a=4;
A执行3,切换,B执行12,切换,A执行4,时间片结束,a=13;
A执行1234,时间片结束,a=15;
A执行34,切换,B执行12,时间片结束,a=26;

线程池

回顾进程池

// g++ test.cc -o test -std=c++11#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include "Task.hpp"
using namespace std;#define PROCESS_NUM 5typedef pair<pid_t, int> slot;void sendCommand(int fatherWriter, uint32_t command_ref)
{// ssize_t write(int __fd, const void *__buf, size_t __n)write(fatherWriter, &command_ref, sizeof(command_ref));
}int getCommand(int childReader, bool &quit)
{uint32_t command_ref = 0;// ssize_t read(int __fd, void *__buf, size_t __nbytes)ssize_t s = read(childReader, &command_ref, sizeof(command_ref));if (s == 0){quit = true;return -1;}assert(s == sizeof(uint32_t));return command_ref;
}int main()
{Load();vector<pair<pid_t, int>> slots;for (int i = 0; i < PROCESS_NUM; i++){int pipefd[2] = {0};int n = pipe(pipefd);assert(n == 0);(void)n;pid_t id = fork();assert(id != -1);if (id == 0){close(pipefd[1]);while (true){bool quit = false;int command_ref = getCommand(pipefd[0], quit);if (quit == true)break;if (command_ref >= 0 && command_ref < cmdSetsize())cmdSet[command_ref]();elsecout << "非法command: " << command_ref << endl;}exit(1);}close(pipefd[0]);slots.push_back(pair<pid_t, int>(id, pipefd[1]));}srand((unsigned long)time(nullptr) ^ getpid() ^ 23323123123L);while (true){int command_ref = rand() % cmdSetsize();int child_ref = rand() % slots.size();sendCommand(slots[child_ref].second, command_ref);cout << "father[" << getpid() << "] call child[" << slots[child_ref].first<< "] execute " << cmdContent[command_ref]<< " through fatherWriter " << slots[child_ref].second << endl;sleep(1);}for (const auto &slot : slots)close(slot.second);for (const auto &slot : slots)waitpid(slot.first, nullptr, WNOHANG);
}

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

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

相关文章

基于ESP32的桌面小屏幕实战[6]:环境搭建和软件基础

摘要 本文分为两部分&#xff1a;Linux开发环境搭建和软件基础。Linux开发环境搭建介绍了Ubuntu虚拟机安装及SSH、Samba配置&#xff0c;可以实现用VSCode操作虚拟机。为了后续工作&#xff0c;搭建了乐鑫ESP32 SDK环境。软件基础介绍了Linux开发常用的软件基础&#xff0c;包…

FreeRtos的使用教程

定义&#xff1a; RTOS实时操作系统, (Real Time Operating System), 指的是当外界事件发生时, 能够有够快的响应速度,调度一切可利用的资源, 控制实时任务协调一致的运行。 特点&#xff1a; 支持多任务管理&#xff0c; 处理多个事件&#xff0c; 实现更复杂的逻辑。 与计算…

【精选】基于数据挖掘的招聘信息分析与市场需求预测系统 职位分析、求职者趋势分析 职位匹配、人才趋势、市场需求分析数据挖掘技术 职位需求分析、人才市场趋势预测

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

Go中的三种锁

Go 中的锁 Go 语言提供了多种锁机制&#xff0c;用于在并发编程中保护共享资源。常见的锁包括 互斥锁、读写锁 和 sync.Map 的安全锁。 1. 互斥锁&#xff08;Mutex&#xff09; 原理 互斥锁&#xff08;sync.Mutex&#xff09;是一种最简单的锁机制&#xff0c;用于保护共…

2025美赛数学建模C题:奥运金牌榜,完整论文代码模型目前已经更新

2025美赛数学建模C题&#xff1a;奥运金牌榜&#xff0c;完整论文代码模型目前已经更新&#xff0c;获取见文末名片

【数据结构】深入解析:构建父子节点树形数据结构并返回前端

树形数据结构列表 一、前言二、测试数据生成三、树形代码3.1、获取根节点3.2、遍历根节点&#xff0c;递归获取所有子节点3.3、排序3.4、完整代码 一、前言 返回前端VO对象中&#xff0c;有列情况列表展示需要带树形结构&#xff0c;例如基于RBAC权限模型中的菜单返回&#xf…

Docker快速部署高效照片管理系统LibrePhotos搭建私有云相册

文章目录 前言1.关于LibrePhotos2.本地部署LibrePhotos3.LibrePhotos简单使用4. 安装内网穿透5.配置LibrePhotos公网地址6. 配置固定公网地址 前言 想象一下这样的场景&#xff1a;你有一大堆珍贵的回忆照片&#xff0c;但又不想使用各种网盘来管理。怎么办&#xff1f;别担心…

【开源免费】基于Vue和SpringBoot的医院资源管理系统(附论文)

本文项目编号 T 161 &#xff0c;文末自助获取源码 \color{red}{T161&#xff0c;文末自助获取源码} T161&#xff0c;文末自助获取源码 目录 一、系统介绍二、数据库设计三、配套教程3.1 启动教程3.2 讲解视频3.3 二次开发教程 四、功能截图五、文案资料5.1 选题背景5.2 国内…

被占用的电脑文件推沟里

在使用电脑的过程中&#xff0c;你是否遇到过这种情况&#xff1a;想删除、移动或重命名一个文件&#xff0c;结果系统无情地告诉你——“文件被占用&#xff0c;无法操作&#xff01;”这时候&#xff0c;IObit Unlocker 就该闪亮登场了&#xff01;这款免费的文件解锁工具&am…

Coze插件开发之基于已有服务创建并上架到扣子商店

Coze插件开发之基于已有服务创建并上架到扣子商店 在应用开发中&#xff0c;需要调用各种插件&#xff0c;以快速进行开发。但有时需要调用的插件在扣子商店里没有&#xff0c;那怎么办呢&#xff1f; 今天就来带大家快速基于已有服务创建一个新的插件 简单来讲&#xff0c;就是…

微信小程序-点餐(美食屋)02开发实践

目录 概要 整体架构流程 &#xff08;一&#xff09;用户注册与登录 &#xff08;二&#xff09;菜品浏览与点餐 &#xff08;三&#xff09;订单管理 &#xff08;四&#xff09;后台管理 部分代码展示 1.index.wxml 2.list.wxml 3.checkout.wxml 4.detail.wxml 小结优点 概要…

[Dialog屏幕开发] 屏幕绘制(下拉菜单)

阅读该篇文章之前&#xff0c;可先阅读下述资料 [Dialog屏幕开发] Table Control 列数据操作https://blog.csdn.net/Hudas/article/details/145343731?spm1001.2014.3001.5501上篇文章我们的屏幕已实现了如下功能 我们已经设置了按钮对Table Control 列的数据进行了操作 接下…

类与对象(下)

再谈构造函数 构造函数体赋值 在创建对象时&#xff0c;编译器通过调用构造函数&#xff0c;给对象中各个成员变量一个合适的初始值 class Date { public: Date(int year, int month, int day){_year year;_month month;_day day;} private: int _year; int _month; int _d…

【机器学习】深入探索SVM:支持向量机的原理与应用

目录 &#x1f354; SVM引入 1.1什么是SVM? 1.2支持向量机分类 1.3 线性可分、线性和非线性的区分 &#x1f354; 小结 学习目标 知道SVM的概念 &#x1f354; SVM引入 1.1什么是SVM? 看一个故事&#xff0c;故事是这样子的&#xff1a; 在很久以前的情人节&#xf…

Dev-C++分辨率低-解决办法

目录 【工具】Dev-C分辨率低-解决办法问题背景完整操作指南第一步&#xff1a;打开属性设置 【工具】Dev-C分辨率低-解决办法 问题背景 Dev-C因版本老旧&#xff08;长期未更新&#xff09;&#xff0c;在高分辨率显示器上存在界面模糊问题。通过修改Windows兼容性设置可优化…

[VSCode] vscode下载安装及安装中文插件详解(附下载链接)

VSCode 是一款由微软开发且跨平台的免费源代码编辑器&#xff1b;该软件支持语法高亮、代码自动补全、代码重构、查看定义功能&#xff0c;并且内置了命令行工具和Git版本控制系统。 下载链接&#xff1a;https://pan.quark.cn/s/3a90aef4b645 提取码&#xff1a;NFy5 通过上面…

javascript-es6 (一)

作用域&#xff08;scope&#xff09; 规定了变量能够被访问的“范围”&#xff0c;离开了这个“范围”变量便不能被访问 局部作用域 函数作用域&#xff1a; 在函数内部声明的变量只能在函数内部被访问&#xff0c;外部无法直接访问 function getSum(){ //函数内部是函数作用…

c语言中的数组(上)

数组的概念 数组是⼀组相同类型元素的集合&#xff1b; 数组中存放的是1个或者多个数据&#xff0c;但是数组元素个数不能为0。 数组中存放的多个数据&#xff0c;类型是相同的。 数组分为⼀维数组和多维数组&#xff0c;多维数组⼀般⽐较多⻅的是⼆维数组。 数组创建 在C语言…

C#,入门教程(05)——Visual Studio 2022源程序(源代码)自动排版的功能动画图示

上一篇&#xff1a; C#&#xff0c;入门教程(04)——Visual Studio 2022 数据编程实例&#xff1a;随机数与组合https://blog.csdn.net/beijinghorn/article/details/123533838https://blog.csdn.net/beijinghorn/article/details/123533838 新来的徒弟们交上来的C#代码&#…

用Python和PyQt5打造一个股票涨幅统计工具

在当今的金融市场中&#xff0c;股票数据的实时获取和分析是投资者和金融从业者的核心需求之一。无论是个人投资者还是专业机构&#xff0c;都需要一个高效的工具来帮助他们快速获取股票数据并进行分析。本文将带你一步步用Python和PyQt5打造一个股票涨幅统计工具&#xff0c;不…