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

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

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

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

线程:
    1> 线程是轻量级的进程
    2> 线程的体现是函数
        -- 在一个进程中,多个函数通知执行(多线程的任务)
    3> 线程依赖于进程
    4> 线程是CPU调度和执行的最小单位
    5> 同一个进程中的线程,共享该进程的地址空间
    6> 多线程通过第三方的线程库来实现 -- pthread
    7> 线程创建的上限受栈空间影响
        ulimit -a
      stack size              (kbytes, -s) 8192


    1- 一个进程中的多个线程共享以下资源
            可执行的指令
            静态数据
            进程中打开的文件描述符
            信号处理函数
            当前工作目录
            用户ID
            用户组ID

    2- 每个线程私有的资源如下
            线程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.   
   编译线程的程序需要加 -lpthread
       gcc 1-pthread_creat.c -lpthread
   
   进程的结束是判断main函数是否结束,如果main函数结束,
   进程会直接结束,会直接回收其他线程的内容

创建进程:

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

#if 0
  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
          失败:一个错误号
#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 0
            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
                    失败:一个错误号
#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 0
    char *retval = NULL;    //避免野指针的出现
    pthread_join(tid,(void **)&retval); //接收遗言
    printf("%s\n",retval);
#endif

#if 1
    int *retval;
    pthread_join(tid,(void **)&retval); //接收遗言
    printf("%d\n",*retval);
#endif
    printf("------------------------------\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 0
   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
           失败:一个错误号
#endif

//线程处理函数:(名称自定义)
//返回值类型必须是void *
//参数列表只能有一个 void *类型的参数
void *pthread_task(void *arg)
{
    printf("%ld -- 开启\n",pthread_self());    //pthread_self()获取自己的tid
    printf("%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 0
    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
            失败:一个错误号
#endif

//线程处理函数:(名称自定义)
//返回值类型必须是void *
//参数列表只能有一个 void *类型的参数
void *pthread_task(void *arg)
{
    printf("%ld -- 开启\n",pthread_self());    //pthread_self()获取自己的tid
    printf("%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 0
   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
           失败:一个错误号
#endif

//线程处理函数:(名称自定义)
//返回值类型必须是void *
//参数列表只能有一个 void *类型的参数
void *pthread_task(void *arg)
{
    printf("%ld -- 开启\n",pthread_self());    //pthread_self()获取自己的tid
    printf("%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
                  出错:-1
      
      2> 上锁 然后 解锁
          #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操作 -1
        printf("我是你爹,不停你的\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);
}

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

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

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

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

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

相关文章

day16_推荐系统和总结

文章目录 day16_推荐系统和总结一、推荐实现1、基于流行度推荐&#xff08;掌握&#xff09;1.1 近期热门商品推荐1.2 个人热门商品推荐 2、基于隐语义模型的协同过滤推荐&#xff08;了解&#xff09;2.1 ALS算法介绍2.2 推荐代码 3、基于物品的协同过滤推荐&#xff08;了解&…

深度解析应用层协议-----HTTP与MQTT(涵盖Paho库)

HTTP协议概述 1.1 HTTP的基本概念 HTTP是一种应用层协议&#xff0c;使用TCP作为传输层协议&#xff0c;默认端口是80&#xff0c;基于请求和响应的方式&#xff0c;即客户端发起请求&#xff0c;服务器响应请求并返回数据&#xff08;HTML&#xff0c;JSON&#xff09;。在H…

redis的应用,缓存,分布式锁

1.应用 1.1可以用作缓存 作用&#xff1a;提交数据的查询效率&#xff0c;减少对数据库的访问频率 什么数据适合放入缓存 1.查询频率高&#xff0c;修改频率低 2.对安全系数比较低 如何实现 Service public class DeptServer {Autowiredprivate DeptMapper deptMapper;Auto…

Ubuntu22.04 - etcd的安装和使用

目录 介绍安装Etcd安装etcd的客户端使用 介绍 Etcd 是一个 golang 编写的分布式、高可用的一致性键值存储系统&#xff0c;用于配置共享和服务发现等。它使用 Raft 一致性算法来保持集群数据的一致性&#xff0c;且客户端通过长连接watch 功能&#xff0c;能够及时收到数据变化…

对学习编程语言的一些理解

目录 一、代码运行的过程 二、跨平台的实现 1&#xff09;C/C 2&#xff09;C# 3&#xff09;Java 三、总结 一、代码运行的过程 开发程序无论使用何种编程语言&#xff0c;至少都需要经历编码、编译、连接和运行这么4个过程&#xff0c;C语言是这样&#xff0c;Java语言…

【知识】深度学习中,应该先zero_grad还是先backward?

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 抛出问题 各大GPT的回答 ChatGPT-4o ChatGPT-o3-mini-high Kimi-长思考 Deepseek-R1 Grok3 Pytorch官方教程中 抛出问题 以下哪种方式是…

kafka消费能力压测:使用官方工具

背景 在之前的业务场景中&#xff0c;我们发现Kafka的实际消费能力远低于预期。尽管我们使用了kafka-go组件并进行了相关测试&#xff0c;测试情况见《kafka-go:性能测试》这篇文章。但并未能准确找出消费能力低下的原因。 我们曾怀疑这可能是由我的电脑网络带宽问题或Kafka部…

蓝桥云客 路径之谜

11.路径之谜 - 蓝桥云课 路径之谜 题目描述 小明冒充X星球的骑士&#xff0c;进入了一个奇怪的城堡。 城堡里边什么都没有&#xff0c;只有方形石头铺成的地面。 假设城堡地面是nn个方格。如下图所示。 按习俗&#xff0c;骑士要从西北角走到东南角。可以横向或纵向移动&…

Oracle 深入理解Lock和Latch ,解析访问数据块全流程

Oracle 锁机制介绍 根据保护对象的不同&#xff0c;单实例Oracle数据库锁可以分为以下几大类&#xff1a; DML lock&#xff08;data locks&#xff0c;数据锁&#xff09;&#xff1a;用于保护数据的完整性&#xff1b; DDL lock&#xff08;dictionary locks&#xff0c;字典…

Jenkins 环境搭建---基于 Docker

前期准备 提前安装jdk、maven、nodeJs&#xff08;如果需要的话&#xff09; 创建 jenkins 环境目录&#xff0c;用来当做挂载卷 /data/jenkins/ 一&#xff1a;拉取 Jenkins 镜像 docker pull jenkins/jenkins:lts 二&#xff1a;设置 Jenkins挂载目录 mkdir -p ~/jen…

DOS网络安全

ping -t 不间断地ping目标主机&#xff0c;直到用户用ctrlc键强行终止。经常用来排除网络故障 -l 定制ping信息包的容量,最大上限是65500字节 -n 向远程主机发送的数据 包个数&#xff0c;默认是4。 语法&#xff1a; ping 参数 IP地址 netstat -a 显示所有连接…

QML Component 与 Loader 结合动态加载组件

在实际项目中&#xff0c;有时候我们写好一个组件&#xff0c;但不是立即加载出来&#xff0c;而是触发某些条件后才动态的加载显示出来&#xff0c;当处理完某些操作后&#xff0c;再次将其关闭掉&#xff1b; 这样的需求&#xff0c;可以使用 Component 包裹着组件&#xff…

在 Mac ARM 架构的 macOS 系统上启用 F1 键作为 Snipaste 的截屏快捷键

在 Mac ARM 架构的 macOS 系统上启用 F1 键作为 Snipaste 的截屏快捷键&#xff0c;主要涉及到两个方面&#xff1a;确保 F1 键作为标准功能键工作 和 在 Snipaste 中设置 F1 为快捷键。 因为 Mac 默认情况下&#xff0c;F1-F12 键通常用作控制屏幕亮度、音量等系统功能的快捷键…

vue3学习1

vite是新的官方构建工具&#xff0c;构建速度比webpack更快 vue项目的入口文件是index.html&#xff0c;一般在这里引入src/main.js&#xff0c;并且设置好容器#app App.vue放的是根组件&#xff0c;components里放分支组件 vue组件中写三种标签&#xff0c;template & s…

istio实现灰度发布,A/B发布, Kiali网格可视化(二)

代码发布是软件开发生命周期中的一个重要环节&#xff0c;确保新功能和修复能够顺利上线。以下是几种常见的代码发布流程。在学习灰度发布之前。我们首先回忆下代码发布常用的几种方法。 A/B&#xff08;蓝绿&#xff09;发布&#xff1a; 蓝绿部署是一种通过维护两套独立的环…

MySQL日志undo log、redo log和binlog详解

MySQL 日志&#xff1a;undo log、redo log、binlog 有什么用&#xff1f; 一、前言 在MySQL数据库中&#xff0c;undo log、redo log和binlog这三种日志扮演着至关重要的角色&#xff0c;它们各自承担着不同的功能&#xff0c;共同保障了数据库的正常运行和数据的完整性。了解…

DeepSeek接入Siri(已升级支持苹果手表)完整版硅基流动DeepSeek-R1部署

DeepSeek接入Siri&#xff08;已升级支持苹果手表&#xff09;完整版硅基流动DeepSeek-R1部署 **DeepSeek** 是一款专注于深度学习和人工智能的工具或平台&#xff0c;通常与人工智能、机器学习、自动化分析等领域有关。它的主要功能可能包括&#xff1a;深度学习模型搜索&…

电力通信物联网应用,国密网关守护电力数据安全

电力国密网关是用于保护电力调度数据网路由器和电力系统的局域网之间通信安全的电力专用网关机&#xff0c;主要为上下级控制系统之间的广域网通信提供认证与加密服务&#xff0c;实现数据传输的机密性、完整性。 国密算法网关功能特点 身份认证&#xff1a;对接入的设备和用户…

overflow-x: auto 使用鼠标实现横向滚动,区分触摸板和鼠标滚动事件的方法

假设一个 div 的滚动只设置了 overflow-x: auto 我们发现使用鼠标的滚轮是无法左右滚动的&#xff0c;但是使用笔记本电脑的触摸板&#xff0c;或者在移动设备上是可以滚动的。所以我们需要兼容一下鼠标的横向滚动功能。 我们可以监控 wheel 事件&#xff0c;然后根据位置来计…

基于STM32单片机的智能蔬菜大棚温湿度监测系统设计

引言 在现代农业生产中&#xff0c;温湿度、光照强度和土壤湿度等环境因素对植物的生长起着至关重要的作用。智能蔬菜大棚正是基于这些因素&#xff0c;通过自动化控制和远程监控技术&#xff0c;实现对植物生长环境的精准管理&#xff0c;最终提升蔬菜的产量和质量。本文介绍…