【进程与线程】Linux 线程、同步以及互斥

每个用户进程有自己的地址空间。

线程是操作系统与多线程编程的基础知识。
系统为每个用户进程创建一个 task_struct 来描述该进程:该结构体中包含了一个指针指向该进程的虚拟地址空间映射表:
请添加图片描述
实际上 task_struct 和地址空间映射表一起用来表示一个进程。由于进程的地址空间是私有的,因此在进程间上下文切换时,系统开销比较大,因此,为了提高系统的性能,许多操作系统规范里引入了轻量级进程的概念,也被称为线程。在同一个进程中创建的线程共享该进程的地址空间,Linux里同样用task_struct 来描述一个线程。线程和进程都参与统一的调度。

通常线程指的是共享相同地址空间的多个任务:
请添加图片描述

线程:1> 线程是轻量级的进程2> 线程的体现是函数-- 在一个进程中,多个函数通知执行(多线程的任务)3> 线程依赖于进程4> 线程是CPU调度和执行的最小单位5> 同一个进程中的线程,共享该进程的地址空间6> 多线程通过第三方的线程库来实现 -- pthread7> 线程创建的上限受栈空间影响ulimit -astack size              (kbytes, -s) 81921- 一个进程中的多个线程共享以下资源可执行的指令静态数据进程中打开的文件描述符信号处理函数当前工作目录用户ID用户组ID2- 每个线程私有的资源如下线程ID (TID) -- gettid()  pthread_self()PC(程序计数器)和相关寄存器堆栈局部变量返回地址错误号 (errno)信号掩码和优先级  0-144执行状态和属性

多线程通过第三方的线程库来实现:New POSIX Thread Library (NPTL)

  • 是早期Linux Threads的改进
  • 采用1:1的线程模型
  • 显著的提高了运行效率
  • 信号处理效率更高

接下来我们会详细的介绍线程共享资源和私有资源,还有线程库如pthread,以及线程的限制如栈空间。

线程(Thread)

线程是操作系统进行 CPU 调度和执行的最小单位,是轻量级的进程(Lightweight Process),允许多个执行流在同一个进程内并发运行。

一、线程的核心特性
  1. 轻量级进程
    • 线程共享进程的地址空间和资源,创建、切换、销毁的开销远小于进程
    • 示例:一个浏览器进程可包含多个线程(渲染线程网络线程UI线程)。
  2. 线程的体现是函数
    • 线程的执行入口是一个函数,多个线程对应多个并行执行的函数。
    • 示例代码(POSIX线程):
#include <pthread.h>
void *thread_func(void *arg) {printf("Thread ID: %lu\n", pthread_self());return NULL;
}
int main() {pthread_t tid;pthread_create(&tid, NULL, thread_func, NULL);pthread_join(tid, NULL);return 0;
}
  1. 依赖进程存在
    • 线程无法独立存在,必须依附于进程。
    • 进程终止时,所有线程会被强制终止。
  2. CPU 调度的最小单位
    • 操作系统调度器直接管理线程,而非进程。
    • 线程的优先级和调度策略可独立设置(如 SCHED_FIFO, SCHED_RR)。
  3. 共享进程地址空间
    • 同一进程的所有线程共享:
      • 代码段:可执行指令。
      • 数据段:全局变量、静态变量。
      • 堆栈:动态分配的内存。
      • 文件描述符:打开的文件、网络套接字。
  4. 第三方线程库实现
    • POSIX 线程库pthread):Linux/Unix 标准实现。
    • Windows APICreateThread 函数。
  5. 线程创建限制
    • 线程数量受 栈空间大小 限制(通过 ulimit -s 查看)。
    • 默认栈大小(Linux 通常为 8MB),可通过属性调整:
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 2 * 1024 * 1024); // 2MB
pthread_create(&tid, &attr, thread_func, NULL);
二、线程的共享资源与私有资源
  1. 共享资源(进程级别)
资源类型说明
可执行指令进程的代码段(.text
静态数据全局变量、静态变量(.data, .bss
文件描述符打开的文件、管道、套接字
信号处理函数signal()sigaction() 注册的处理函数
当前工作目录进程的 cwd(可通过 chdir() 修改)
用户ID和组ID进程的权限身份
  1. 私有资源(线程级别)
资源类型说明
线程ID(TID)内核级ID(gettid())或用户级ID(pthread_self()
程序计数器(PC)当前执行的指令地址
寄存器状态CPU 寄存器的当前值(如 EAX, EBX)
堆栈存储局部变量、函数调用链
局部变量函数内定义的自动变量
错误号(errno)线程独立的错误状态码
信号掩码pthread_sigmask() 设置的阻塞信号集合
调度优先级范围通常为 0(最低)到 99(实时优先级)
执行状态运行、就绪、阻塞等
三、线程与进程的对比
特性进程线程
资源开销高(独立地址空间、文件描述符)低(共享进程资源)
创建速度慢(需复制父进程资源)快(仅分配栈和少量数据结构)
通信机制复杂(管道、共享内存等)简单(共享全局变量)
容错性高(一个进程崩溃不影响其他)低(一个线程崩溃导致进程终止)
CPU 利用率低(上下文切换开销大)高(切换快速)

线程的应用场景:

  1. 高并发服务器:每个客户端连接由一个线程处理(如 Web 服务器 Apache)。
  2. 实时数据处理:数据采集线程 + 处理线程 + 存储线程(如音视频流处理)。
  3. GUI 应用程序:UI 主线程 + 后台计算线程(避免界面卡顿)。
  4. 并行计算:多线程分割任务加速计算(如矩阵运算)。

四、线程的操作(线程API都是pthread_ 开头)

  1. 创建新线程
#include <pthread.h>
typedef void *(*start_routine) (void *) //一个函数指针指向指针函数
   要求:1> 返回值类型 void * 2> 参数列表 只能有一个 void * 类型的参数int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);参数:thread:线程的ID号attr:线程的属性 NULL 表示使用默认属性start_routine:线程处理函数 -- 专门处理多线程任务的arg:传递给线程处理函数的参数 NULL 表示不需要传参返回值:成功: 0失败:一个错误号Compile and link with -pthread.   编译线程的程序需要加 -lpthreadgcc 1-pthread_creat.c -lpthread进程的结束是判断main函数是否结束,如果main函数结束,进程会直接结束,会直接回收其他线程的内容

创建进程:

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>#if 0int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);参数:thread:线程的ID号attr:线程的属性 NULL 表示使用默认属性start_routine:线程处理函数 -- 专门处理多线程任务的arg:传递给线程处理函数的参数  NULL 表示不需要传参返回值:成功: 0失败:一个错误号
#endif
typedef struct
{int a;float b;
}Stu_t;
//线程处理函数:(名称自定义)
//返回值类型必须是void *
//参数列表只能有一个 void *类型的参数
void *pthread_task(void *arg)
{//printf("收到大哥的指示:%c\n",*(char *)arg);    //接收单个字符//printf("收到大哥的指示:%d\n",*(int *)arg);       //接收int数据//printf("收到大哥的指示:%s\n",(char *)arg);       //接收字符串Stu_t *temp = (Stu_t *)arg;printf("%d   %f \n",temp->a,temp->b);   //接收一个结构体return (void *)0;
}int main()
{int n = 7;char c = 'a';char str[] = "hello\n";Stu_t s1 = {10,12.34};//func();//开新线程pthread_t tid;  //接收新线程的tid//int ret = pthread_create(&tid,NULL,pthread_task,NULL ); // 这句话之后线程处理函数开始运行//int ret = pthread_create(&tid,NULL,pthread_task, &c);   //传递单个字符//int ret = pthread_create(&tid,NULL,pthread_task, &n);   //传递一个int 类型的数据//int ret = pthread_create(&tid,NULL,pthread_task, str);  //传递一个字符串int ret = pthread_create(&tid,NULL,pthread_task, &s1);    //传递一个结构体if(ret != 0){perror("pthread_create");return -1;}sleep(1);
}
  1. 线程的退出
#include <pthread.h>void pthread_exit(void *retval);
//	参数:
//		retval:遗言
//		返回值:无Compile and link with -pthread.
  1. 线程的等待收尸
#include <pthread.h>
//阻塞当前线程,等到指定线程结束
int pthread_join(pthread_t thread, void **retval);
//   参数:
//        thread:线程号
//        retval:接收线程的遗言 NULL 表示不关系
//    返回值:
//        成功: 0
//        失败:一个错误号Compile and link with -pthread.

杀死进程与收尸:

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>#if 0int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);参数:thread:线程的ID号attr:线程的属性 NULL 表示使用默认属性start_routine:线程处理函数 -- 专门处理多线程任务的arg:传递给线程处理函数的参数  NULL 表示不需要传参返回值:成功: 0失败:一个错误号
#endif//线程处理函数:(名称自定义)
//返回值类型必须是void *
//参数列表只能有一个 void *类型的参数
void *pthread_task(void *arg)
{int n = 7;while(n--){printf("笑死,我是你爹\n");sleep(1);}// return (void *)0;//pthread_exit(NULL);   //退出当前线程,没有遗言//pthread_exit("穿山甲到底说了啥");    //退出当前线程,有遗言static int a = 100;pthread_exit((void *)(&a));}
int main()
{int n = 7;//开新线程pthread_t tid;//开启新线程 pthread_task 采用默认属性,不传递参数int ret = pthread_create(&tid,NULL,pthread_task,NULL ); // 这句话之后线程处理函数开始运行if(ret != 0){perror("pthread_create");return -1;}while(n--){printf("我是老大\n");}//pthread_join(tid,NULL); //阻塞当前线程,等待指定tid的线程结束 NULL不关系遗言//int pthread_join(pthread_t thread, void **retval);
#if 0char *retval = NULL;    //避免野指针的出现pthread_join(tid,(void **)&retval); //接收遗言printf("%s\n",retval);
#endif#if 1int *retval;pthread_join(tid,(void **)&retval); //接收遗言printf("%d\n",*retval);
#endifprintf("------------------------------\n");pthread_exit(0);
}
  1. 线程的属性设置

分离属性:线程结束后,线程自动回收自己空间。
非分离属性(默认): 线程结束后,最后同一由 main() 线程收尸。

如何设置设置线程属性?1> 先创建线程,在设置#include <pthread.h>//将指定的线程设置为分离属性int pthread_detach(pthread_t thread);参数:thread:线程的ID号返回值:成功: 0失败:一个错误号Compile and link with -pthread.

非分离属性(默认)示例代码:

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>#if 0int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);参数:thread:线程的ID号attr:线程的属性 NULL 表示使用默认属性start_routine:线程处理函数 -- 专门处理多线程任务的arg:传递给线程处理函数的参数  NULL 表示不需要传参返回值:成功: 0失败:一个错误号
#endif//线程处理函数:(名称自定义)
//返回值类型必须是void *
//参数列表只能有一个 void *类型的参数
void *pthread_task(void *arg)
{printf("%ld -- 开启\n",pthread_self());    //pthread_self()获取自己的tidprintf("%ld -- 关闭\n",pthread_self());
}int main()
{//func();//开新线程pthread_t tid;int count = 0;while(1){//开启新线程 pthread_task 采用默认属性,不传递参数int ret = pthread_create(&tid,NULL,pthread_task,NULL ); // 这句话之后线程处理函数开始运行if(ret != 0){perror("pthread_create");return -1;}printf("cout  = %d\n",count ++);}
}

分离属性示例代码:

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>#if 0int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);参数:thread:线程的ID号attr:线程的属性 NULL 表示使用默认属性start_routine:线程处理函数 -- 专门处理多线程任务的arg:传递给线程处理函数的参数  NULL 表示不需要传参返回值:成功: 0失败:一个错误号
#endif//线程处理函数:(名称自定义)
//返回值类型必须是void *
//参数列表只能有一个 void *类型的参数
void *pthread_task(void *arg)
{printf("%ld -- 开启\n",pthread_self());    //pthread_self()获取自己的tidprintf("%ld -- 关闭\n",pthread_self());
}int main()
{//func();//开新线程pthread_t tid;int count = 0;while(1){//开启新线程 pthread_task 采用默认属性,不传递参数int ret = pthread_create(&tid,NULL,pthread_task,NULL ); // 这句话之后线程处理函数开始运行if(ret != 0){perror("pthread_create");return -1;}//int pthread_detach(pthread_t thread);pthread_detach(tid);    //将指定tid的线程变为分离属性printf("cout  = %d\n",count ++);}
}
 	2> 先设置属性,在创建线程#include <pthread.h>//设置初始属性int pthread_attr_init(pthread_attr_t *attr);参数:attr:线程属性//销毁属性int pthread_attr_destroy(pthread_attr_t *attr);#include <pthread.h>//设置线程的分离属性int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);参数:attr:线程属性detachstate:要设置的属性PTHREAD_CREATE_DETACHED:设置线程的分离属性PTHREAD_CREATE_JOINABLE:设置线程的非分离属性返回值:成功: 0失败:一个错误号Compile and link with -pthread.

pthread_attr_init.c(先设置属性,在创建线程)

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>#if 0int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);参数:thread:线程的ID号attr:线程的属性 NULL 表示使用默认属性start_routine:线程处理函数 -- 专门处理多线程任务的arg:传递给线程处理函数的参数  NULL 表示不需要传参返回值:成功: 0失败:一个错误号
#endif//线程处理函数:(名称自定义)
//返回值类型必须是void *
//参数列表只能有一个 void *类型的参数
void *pthread_task(void *arg)
{printf("%ld -- 开启\n",pthread_self());    //pthread_self()获取自己的tidprintf("%ld -- 关闭\n",pthread_self());
}int main()
{//1- 初始化线程属性//int pthread_attr_init(pthread_attr_t *attr);pthread_attr_t attr;    //存放线程属性的参数if(pthread_attr_init(&attr)!= 0)    //将默认属性给到attr{perror("pthread_attr_init");return -1;}//2-设置线程的分离属性//int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
/*PTHREAD_CREATE_DETACHED:设置线程的分离属性PTHREAD_CREATE_JOINABLE:设置线程的非分离属性
*/   if(pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED)!= 0)     //将线程属性设置为分离属性{perror("pthread_attr_setdetachstate");return -1;}//开新线程pthread_t tid;int count = 0;while(1){//开启新线程 pthread_task 采用指定的属性 -- attr,不传递参数int ret = pthread_create(&tid,&attr,pthread_task,NULL ); // 这句话之后线程处理函数开始运行if(ret != 0){perror("pthread_create");return -1;}printf("cout  = %d\n",count ++);}
}

五、线程的同步以及互斥

线程同步机制
由于线程共享内存,需同步机制避免竞态条件:
多线程共享同一个进程的地址空间,优点:线程间很容易进行通信,通过全局变量实现数据共享和交换。缺点:多个线程同时访问共享对象时需要引入同步和互斥机制

同步(synchronization) 指的是多个任务(线程)按照约定的顺序相互配合完成一件事情,1968年,Edsgar Dijkstra 基于信号量的概念提出了一种同步机制,由信号量来决定线程是继续运行还是阻塞等待。

信号量代表某一类资源,其值表示系统中该资源的数量。信号量是一个受保护的变量,只能通过三种操作来访问:

  1. 初始化

  2. P操作(申请资源)

     P(S) 含义如下:if (信号量的值大于0) { 申请资源的任务继续运行;信号量的值减一;}else { 申请资源的任务阻塞;} 
    
  3. V操作(释放资源)

     V(S) 含义如下:if (没有任务在等待该资源) { 信号量的值加一;}else { 唤醒第一个等待的任务,让其继续运行}
    

注意:信号量的值为非负整数。

Posix Semaphore API

  • posix中定义了两类信号量:
    • 无名信号量(基于内存的信号量)
    • 有名信号量

pthread 库常用的信号量操作函数如下:

  • int sem_init(sem_t *sem, int pshared, unsigned int value);
  • int sem_wait(sem_t *sem); // P操作
  • int sem_post(sem_t *sem); // V操作
  • int sem_trywait(sem_t *sem);
  • int sem_getvalue(sem_t *sem, int *svalue);
线程间互斥

引入互斥(mutual exclusion)锁 的目的是用来保证共享数据操作的完整性。互斥锁主要用来保护临界资源,每个临界资源都由一个互斥锁来保护,任何时刻最多只能有一个线程能访问该资源。线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止。

  1. 互斥锁(Mutex)
pthread_mutex_t lock;
pthread_mutex_lock(&lock);
// 临界区
pthread_mutex_unlock(&lock);

线程的互斥操作 —— 互斥锁:

线程的互斥操作 -- 互斥锁1> 申请互斥锁#include <pthread.h>//初始化互斥锁int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);参数:mutex:互斥锁变量attr:互斥锁的属性 NULL 表示默认属性返回值:成功:0出错:-12> 上锁 然后 解锁#include <pthread.h>//给临界资源上锁int pthread_mutex_lock(pthread_mutex_t *mutex);//给临界资源解锁int pthread_mutex_unlock(pthread_mutex_t *mutex);参数:mutex:互斥锁变量返回值:成功:0失败:错误号
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>pthread_mutex_t mutex1; //互斥锁变量int a = 0;
int b = 0;//线程处理函数:(名称自定义)
//返回值类型必须是void *
//参数列表只能有一个 void *类型的参数
void *pthread_task1(void *arg)
{while(1){//上锁 ---》 让其变成临界资源pthread_mutex_lock(&mutex1);/*临界区*/a++;b++;/*---------*///解锁 -- 》解除临界资源pthread_mutex_unlock(&mutex1);}
}
void *pthread_task2(void *arg)
{while(1){if(a == b){printf("a = b = %d\n",a);}else{printf("a = %d,b = %d\n",a,b);}}
}int main()
{//申请互斥锁//int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);if(pthread_mutex_init(&mutex1,NULL) == -1){fprintf(stderr,"pthread_mutex_init error\n");return -1;}//开新线程pthread_t tid1,tid2;//开启新线程 pthread_task 采用默认属性,不传递参数int ret = pthread_create(&tid1,NULL,pthread_task1,NULL ); // 这句话之后线程处理函数开始运行if(ret != 0){perror("pthread_create");return -1;}ret = pthread_create(&tid1,NULL,pthread_task2,NULL ); // 这句话之后线程处理函数开始运行if(ret != 0){perror("pthread_create");return -1;}pthread_join(tid1,NULL);pthread_join(tid2,NULL);
}
  1. 条件变量(Condition Variable)
pthread_cond_t cond;
pthread_cond_wait(&cond, &lock);  // 等待条件
pthread_cond_signal(&cond);       // 通知一个线程
  1. 信号量(Semaphore)
sem_t sem;
sem_wait(&sem);  // P操作
sem_post(&sem);  // V操作

线程的同步机制:

1- 线程的同步 --- 信号量 : 信号量的值为非负整数1> 信号量的初始化#include <semaphore.h>int sem_init(sem_t *sem, int pshared, unsigned int value);参数:sem:信号量的变量pshared:信号量的使用范围0 :线程专用非0 :进程也可以使用value:信号量的值返回值:成功:0失败:-1 ,并且设置错误号Link with -pthread.2> 执行PV操作 ---- p操作 -1#include <semaphore.h>int sem_wait(sem_t *sem);参数:sem:信号量的变量;返回值:成功:0失败:-1 ,并且设置错误号---- V操作 +1#include <semaphore.h>int sem_post(sem_t *sem);参数:sem:信号量的变量;返回值:成功:0失败:-1 ,并且设置错误号Link with -pthread.
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>sem_t sem1,sem2;      //信号量的变量 最好设置为全局变量//线程处理函数:(名称自定义)
//返回值类型必须是void *
//参数列表只能有一个 void *类型的参数
void *pthread_task1(void *arg)
{while(1){printf("我是老大,都听我的\n");sem_post(&sem1); //v操作 +1//sleep(1);}
}
void *pthread_task2(void *arg)
{while(1){sem_wait(&sem1); // p操作 -1printf("我是你爹,不停你的\n");//sleep(1);}
}int main()
{//初始化信号量//int sem_init(sem_t *sem, int pshared, unsigned int value);//sem_init(变量,适用范围,初始值)sem_init(&sem1,0,0);//func();//开新线程pthread_t tid1,tid2;//开启新线程 pthread_task 采用默认属性,不传递参数int ret = pthread_create(&tid1,NULL,pthread_task1,NULL ); // 这句话之后线程处理函数开始运行if(ret != 0){perror("pthread_create");return -1;}ret = pthread_create(&tid1,NULL,pthread_task2,NULL ); // 这句话之后线程处理函数开始运行if(ret != 0){perror("pthread_create");return -1;}pthread_join(tid1,NULL);pthread_join(tid2,NULL);
}

综上。希望该内容能对你有帮助,感谢!

以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。

我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!

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

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

相关文章

实现动态翻转时钟效果的 HTML、CSS 和 JavaScript,附源码

实现动态翻转时钟效果的 HTML、CSS 和 JavaScript 在现代网页设计中&#xff0c;动画效果可以极大地增强用户体验。本文将介绍如何利用 HTML、CSS 和 JavaScript 创建一个动态翻转时钟的效果&#xff0c;模拟经典机械翻页时钟的视觉效果。我们将通过详细的步骤讲解如何实现时钟…

Spring Boot与MyBatis

Spring Boot与MyBatis的配置 一、简介 Spring Boot是一个用于创建独立的、基于Spring的生产级应用程序的框架&#xff0c;它简化了Spring应用的初始搭建以及开发过程。MyBatis是一款优秀的持久层框架&#xff0c;它支持定制化SQL、存储过程以及高级映射。将Spring Boot和MyBa…

1.16作业

1 进注册界面&#xff0c;第一次以为抓包选把isadmin ture了就好 第二次尝试&#xff0c;勾选is admin&#xff0c;有需要invitecode&#xff08;经典&#xff09; 2 p r**5 r**4 - r**3 r**2 - r 2023 q r**5 - r**4 r**3 - r**2 r 2023 n 25066797992811602609904…

LeetCode 2209.用地毯覆盖后的最少白色砖块:记忆化搜索之——深度优先搜索(DFS)

【LetMeFly】2209.用地毯覆盖后的最少白色砖块&#xff1a;记忆化搜索之——深度优先搜索(DFS) 力扣题目链接&#xff1a;https://leetcode.cn/problems/minimum-white-tiles-after-covering-with-carpets/ 给你一个下标从 0 开始的 二进制 字符串 floor &#xff0c;它表示地…

「正版软件」PDF Reader - 专业 PDF 编辑阅读工具软件

PDF Reader 轻松查看、编辑、批注、转换、数字签名和管理 PDF 文件&#xff0c;以提高工作效率并充分利用 PDF 文档。 像专业人士一样编辑 PDF 编辑 PDF 文本 轻松添加、删除或修改 PDF 文档中的原始文本以更正错误。自定义文本属性&#xff0c;如颜色、字体大小、样式和粗细。…

【报错解决】vue打开界面报错Uncaught SecurityError: Failed to construct ‘WebSocket‘

问题描述&#xff1a; vue运行时正常&#xff0c;但是打开页面后报错 Uncaught SecurityError: Failed to construct WebSocket: An insecure WebSocket connection may not be initiated from a page loaded over HTTPS. 解决方案&#xff1a; 在项目列表中的public下的ind…

骶骨神经

骶骨肿瘤手术后遗症是什么_39健康网_癌症 [健康之路]匠心仁术&#xff08;七&#xff09; 勇闯禁区 骶骨肿瘤切除术

wps中的js开发

严格区分大小写 /*** learn_js Macro*/ function test() {Range(D7).Value2Selection.Value2; // Selection.formula "100" }function Workbook_SheetSelectionChange(Sh, Target) {if(Sh.Name Sheet1) {test();}}function test2() {// 把I4单元格及其周边有数的单…

书生大模型实战营12-InternVL 多模态模型部署微调

文章目录 L2——进阶岛InternVL 部署微调实践0.开发机创建与使用1.环境配置1.1.训练环境配置1.2.推理环境配置 2.LMDeploy部署2.1.LMDeploy基本用法介绍2.2.网页应用部署体验2.3 出错解决2.3.1 问题12.3.2 问题2 3.XTuner微调实践3.1.准备基本配置文件3.2.配置文件参数解读3.3.…

深度学习之图像回归(二)

前言 这篇文章主要是在图像回归&#xff08;一&#xff09;的基础上对该项目进行的优化。&#xff08;一&#xff09;主要是帮助迅速入门 理清一个深度学习项目的逻辑 这篇文章则主要注重在此基础上对于数据预处理和模型训练进行优化前者会通过涉及PCA主成分分析 特征选择 后…

安科瑞能源物联网平台助力企业实现绿色低碳转型

安科瑞顾强 随着全球能源结构的转型和“双碳”目标的推进&#xff0c;能源管理正朝着智能化、数字化的方向快速发展。安科瑞电气股份有限公司推出的微电网智慧能源管理平台&#xff08;EMS 3.0&#xff09;&#xff0c;正是这一趋势下的创新解决方案。该平台集成了物联网&…

基于Spring Boot的农产品智慧物流系统设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

论文笔记-WSDM2024-LLMRec

论文笔记-WSDM2024-LLMRec: Large Language Models with Graph Augmentation for Recommendation LLMRec: 基于图增强的大模型推荐摘要1.引言2.前言2.1使用图嵌入推荐2.2使用辅助信息推荐2.3使用数据增强推荐 3.方法3.1LLM作为隐式反馈增强器3.2基于LLM的辅助信息增强3.2.1用户…

优化YOLOv8:如何利用ODConv卷积解决复杂背景下的目标识别问题

文章目录 1. YOLOv8的现状与挑战1.1 ODConv的提出背景1.2 ODConv卷积的原理 2. YOLOv8与ODConv的结合2.1 ODConv集成到YOLOv8中的架构2.2 代码实现示例2.3 性能评估与改进 3. ODConv的实际应用与优化3.1 ODConv在不同数据集上的表现3.1.1 COCO数据集3.1.2 VOC数据集3.1.3 自定义…

DPVS-2:单臂负载均衡测试

上一篇编译安装了DPVS&#xff0c;这一篇开启DPVS的负载均衡测试 &#xff1a; 单臂 FULL NAT模式 拓扑-单臂 单臂模式 DPVS 单独物理机 CLINET&#xff0c;和两个RS都是另一个物理机的虚拟机&#xff0c;它们网卡都绑定在一个桥上br0 &#xff0c; 二层互通。 启动DPVS …

Maven导入hutool依赖报错-java: 无法访问cn.hutool.core.io.IORuntimeException 解决办法

欢迎大家来到我的博客~欢迎大家对我的博客提出指导&#xff0c;有错误的地方会改进的哦~点击这里了解更多内容 目录 一、报错二、解决办法 一、报错 <dependency><groupId>cn.hutool</groupId><artifactId>hutool-captcha</artifactId> </de…

flowable适配达梦数据库

文章目录 适配相关问题无法从数据库产品名称“DM DBMS”中推断数据库类型分析解决 构建ibatis SqlSessionFactory时出错&#xff1a;inStream参数为null分析解决 liquibase相关问题问题一&#xff1a;不支持的数据库 Error executing SQL call current_schema: 无法解析的成员访…

ElasticSearch公共方法封装

业务场景 1、RestClientBuilder初始化&#xff08;同时支持单机与集群&#xff09; 2、发送ES查询请求公共方法封装&#xff08;支持sql、kql、代理访问、集群访问、鉴权支持&#xff09; 3、判断ES索引是否存在&#xff08;/_cat/indices/${indexName}&#xff09; 4、判断ES…

题海拾贝:【枚举】P2010 [NOIP 2016 普及组] 回文日期

Hello大家好&#xff01;很高兴我们又见面啦&#xff01;给生活添点passion&#xff0c;开始今天的编程之路&#xff01; 我的博客&#xff1a;<但凡. 我的专栏&#xff1a;《编程之路》、《数据结构与算法之美》、《题海拾贝》 欢迎点赞&#xff0c;关注&#xff01; 1、题…

《深入理解JVM》实战笔记(二): 类加载机制与类加载器

序言 Java 语言的强大之处之一在于其动态加载的能力&#xff0c;使得 Java 程序可以在运行时加载新的类&#xff0c;而不需要在编译时确定所有的类信息。这一切都离不开 JVM 的类加载机制。本篇博客将详细探讨 JVM 的类加载过程以及类加载器的工作原理&#xff0c;帮助你更深入…