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

news2025/3/13 11:30:28

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

线程是操作系统与多线程编程的基础知识。
系统为每个用户进程创建一个 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);
}

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

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

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

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

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

相关文章

胶囊网络动态路由算法:突破CNN空间局限性的数学原理与工程实践

一、CNN的空间局限性痛点解析 传统CNN的瓶颈&#xff1a; 池化操作导致空间信息丢失&#xff08;最大池化丢弃85%激活值&#xff09;无法建模层次空间关系&#xff08;旋转/平移等变换不敏感&#xff09;局部感受野限制全局特征整合 示例对比&#xff1a; # CNN最大池化示例…

当pcie设备变化时centos是否会修改网络设备的名称(AI回答)

当pcie设备变化时centos是否会修改网络设备的名称 在CentOS&#xff08;以及其他基于Linux的操作系统&#xff09;中&#xff0c;网络接口的命名通常遵循特定的规则&#xff0c;尤其是在使用PCIe设备&#xff08;如网络适配器&#xff09;时。网络接口的命名通常基于设备的物理…

【做一个微信小程序】校园地图页面实现

前言 上一个教程我们实现了小程序的一些的功能&#xff0c;有背景渐变色&#xff0c;发布功能有的呢&#xff0c;已支持图片上传功能&#xff0c;表情和投票功能开发中&#xff08;请期待&#xff09;。下面是一个更高级的微信小程序实现&#xff0c;包含以下功能&#xff1a;…

Web后端 - Maven管理工具

一 Maven简单介绍 Maven是apache旗下的一个开源项目&#xff0c;是一款用于管理和构建java项目的工具。 Maven的作用 二 Maven 安装配置 依赖配置 依赖传递 依赖范围 生命周期 注意事项&#xff1a;在同一套生命周期中&#xff0c;当运行后面的阶段时&#xff0c;前面的阶段都…

20250217-POMO笔记

文章目录 前言一、伪代码一&#xff1a;POMO Training二、伪代码二&#xff1a;POMO Inference三、POMO注意力模型3.1、自注意力机制3.2、AM模型 前言 以下主要讲解两个算法的伪代码以及注意力模型。 一、伪代码一&#xff1a;POMO Training POMO Training是POMO模型训练的伪…

JavaEE-SpringBoot快速入门

文章目录 本节目标Maven什么是Maven创建一个Maven项目maven项目功能maven的依赖管理全球仓库, 私服, 本地服务器, 配置国内镜像 第一个SpringBoot项目创建项目运行SpringBoot程序 SpringBoot原理初步Web服务器 总结 本节目标 了解什么是maven, 配置国内源使用Springboot创建项…

游戏引擎学习第107天

仓库:https://gitee.com/mrxiao_com/2d_game_2 回顾我们之前停留的位置 在这段内容中&#xff0c;讨论了如何处理游戏中的三维效果&#xff0c;特别是如何处理额外的“Z层”。由于游戏中的艺术资源是位图而不是3D模型&#xff0c;因此实现三维效果变得非常具有挑战性。虽然可…

ComfyUI流程图生图原理详解

一、引言 ComfyUI 是一款功能强大的工具&#xff0c;在图像生成等领域有着广泛应用。本文补充一点ComfyUI 的安装与配置过程遇到的问题&#xff0c;并深入剖析图生图过程及相关参数&#xff0c;帮助读者快速入门并深入理解其原理。 二、ComfyUI 的安装与配置中遇到的问题 &a…

使用右侧值现象来处理一个word导入登记表的需求

需求也简单&#xff0c;导word文件用户登记表&#xff0c;有各部门的十几个版本&#xff08;为什么这么多&#xff1f;不知道&#xff09;。这里说下谈下我的一些代码做法&#xff1a; 需求分析&#xff1a; 如果能解决java字段和各项填的值怎么配对的问题&#xff0c;那么就…

《open3d pyqt》Alpha重建

《open3d pyqt》Alpha重建 一、效果展示二、qt设置2.1 主界面添加动作2.2 dialog 界面、布局如下:三、核心代码一、效果展示 二、qt设置 2.1 主界面添加动作 2.2 dialog 界面、布局如下: 并生成py文件,参考前述章节 三、核心代码 main.py文件增加 from Su

小程序canvas2d实现横版全屏和竖版逐字的签名组件(字帖式米字格签名组件)

文章标题 01 功能说明02 效果预览2.1 横版2.2 竖版 03 使用方式04 横向签名组件源码4.1 html 代码4.2 业务 Js4.3 样式 Css 05 竖向签名组件源码5.1 布局 Html5.2 业务 Js5.3 样式 Css 01 功能说明 技术栈&#xff1a;uniapp、vue、canvas 2d 需求&#xff1a; 实现横版的全…

MoE演变过程

MoE演变过程 1 MoE1.1 BasicMoE1.2 SparseMoE1.2.1 实现 1.3 Shared Expert SparseMoE 1 MoE 参考&#xff1a;https://huggingface.co/blog/zh/moe 1.1 BasicMoE 用router给出各专家的权重&#xff0c;然后让输入过每一个专家&#xff0c;然后做加权求和。 1.2 SparseMoE …

【实战项目】BP神经网络识别人脸朝向----MATLAB实现

(꒪ꇴ꒪ )&#xff0c;Hello我是祐言QAQ我的博客主页&#xff1a;C/C语言&#xff0c;数据结构&#xff0c;Linux基础&#xff0c;ARM开发板&#xff0c;网络编程等领域UP&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff0c;让我们成为一个强大的攻城狮&#xff0…

【1.8w字深入解析】从依赖地狱到依赖天堂:pnpm 如何革新前端包管理?

目录 前言npm 的诞生与发展嵌套依赖模型存在的问题npm3架构与yarnYarn 的诞生与局限Yarn 的诞生背景Yarn 仍然存在的问题 何为幽灵依赖依赖结构的不确定性pnpm王牌登场 -- 网状平铺结构安装包速度快依赖管理软链接 和 硬链接 机制 幽灵依赖产生的根本原因包管理工具的依赖解析机…

137,【4】 buuctf web [SCTF2019]Flag Shop

进入靶场 都点击看看 发现点击work会增加&#xffe5; 但肯定不能一直点下去 抓包看看 这看起来是一个 JWT&#xff08;JSON Web Token&#xff09;字符串。JWT 通常由三部分组成&#xff0c;通过点&#xff08;.&#xff09;分隔&#xff0c;分别是头部&#xff08;Header&…

【c++】c++内存管理

目录 c和c的内存分布回顾C语言动态管理内存的方式malloccallocreallocfree C动态管理内存的方式new和deleteoperator new和operator delete定位new c和c的内存分布 回顾C语言动态管理内存的方式 malloc void* malloc (size_t size);malloc可以在堆上开辟指定内存的空间&#…

EtherNet/IP转Modbus TCP:新能源风电监控与分析实用案例

EtherNet/IP转Modbus TCP&#xff1a;新能源风电监控与分析实用案例 一、案例背景 在某新能源汽车电池生产线上&#xff0c;需要将采用EtherNet/IP协议的电池检测设备与采用ProfiNet协议的生产线控制系统进行集成&#xff0c;以实现对电池生产过程的全面监控和数据采集。 二、…

数字电路-基础逻辑门实验

基础逻辑门是数字电路设计的核心元件&#xff0c;它们执行的是基本的逻辑运算。通过这些基本运算&#xff0c;可以构建出更为复杂的逻辑功能。常见的基础逻辑门包括与门&#xff08;AND&#xff09;、或门&#xff08;OR&#xff09;、非门&#xff08;NOT&#xff09;、异或门…

国产编辑器EverEdit - 如虎添翼的功能:快速选择

1 快速选择 1.1 应用场景 快速选择适用于批量选择和修改的场景&#xff0c;比如&#xff1a;变量改名。 1.2 使用方法 1.2.1 逐项快速选择 将光标放置在单词前或单词中&#xff0c;选择主菜单查找 -> 快速选择 -> 快速选择或使用快捷键Ctrl D 注&#xff1a;光标放…

国内外网络安全政策动态(2025年1月)

▶︎ 1.国家互联网信息办公室发布《个人信息出境个人信息保护认证办法&#xff08;征求意见稿&#xff09;》 1月3日&#xff0c;国家互联网信息办公室发布《个人信息出境个人信息保护认证办法&#xff08;征求意见稿&#xff09;》。根据《意见稿》&#xff0c;个人信息出境个…