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

发布于:2025-02-23 ⋅ 阅读:(16) ⋅ 点赞:(0)

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

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

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

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

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