Linux多进程和多线程(八)多线程

  • 多线程
    • 线程定义
    • 线程与进程
    • 线程资源
  • 线程相关命令
    • pidstat 命令
  • top 命令
  • ps 命令
  • 常见的并发方案
    • 1. 多进程模式
    • 2. 多线程模式
  • 创建线程
    • 1. pthread_create()
      • 示例:创建一个线程
    • 2. pthread_exit() 退出线程
    • 3. pthread_join() 等待线程结束
      • 示例:
    • 线程分离
  • 创建多个线程
    • 示例 1:创建多个线程执行不同的任务
    • 示例 2:创建多个线程执行相同的任务
  • 线程间的通讯
    • 主线程向子线程传递参数
    • 子线程向主线程传递参数
      • 示例:
  • 线程互斥锁
    • 线程互斥锁
    • 互斥锁的原理
    • 互斥锁的特点
    • 互斥锁的使用
      • 静态初始化
      • 动态初始化
        • pthread_mutex_init()函数
        • pthread_mutex_destroy()函数
  • 线程同步
    • ⽣产者与消费者问题
    • 示例 基于互斥锁实现⽣产者与消费者模型
  • 条件变量
    • 条件变量初始化
      • 静态初始化
      • 动态初始化 pthread_cond_init()
      • pthread_cond_destroy()
    • 条件变量的使用
      • 等待 pthread_cond_wait()
      • 通知 pthread_cond_signal()
      • 通知所有 pthread_cond_broadcast()
    • 示例 基于条件变量实现⽣产者与消费者模型

多线程

线程定义

线程是进程中的⼀个执⾏单元,

负责当前进程中程序的执⾏,

⼀个进程中⾄少有⼀个线程

⼀个进程中是可以有多个线程

多个线程共享同一个进程的所有资源,每个线程参与操作系统的统一调度

可以简单理解成 进程 = 内存资源 + 主线程 + 子线程 +…

线程与进程

联系比较紧密的任务,在并发时,优先选择多线程,任务联系不紧密,比较独立的任务,建议选择多进程;

  • 进程:操作系统分配资源的基本单位,是资源分配的最小单位,是程序的执行和调度单位,是程序的运行实例。
  • 线程:是CPU调度和分派的基本单位,是CPU执行的最小单位,是程序执行流的最小单元,是程序执行的最小单位。

线程与进程区别:

  • 内存空间
    • 一个进程中多个线程共享同一个内存空间
    • 多个进程拥有独立的内存空间
  • 进程/线程间通讯
    • 线程间通讯方式简单
    • 进程间通讯方式复杂

线程资源

  • 共享进程的资源
    • 同一块地址空间
    • 文件描述符表
    • 每种信号的处理方式
    • 当前工作目录
    • 用户id和组id
  • 独有资源
    • 线程栈
    • 每个线程都有私有的上下文信息
    • 线程id
    • 寄存器的值
    • errno值
    • 信号屏蔽字以及调度优先级

线程相关命令

在 Linux 系统有很多命令可以查看进程,包括 pidstat 、top 、ps ,可以查看进程,也可以查看一个
进程下的线程

pidstat 命令

ubuntu 下需要安装 sysstat 工具之后,可以支持 pidstat

sudo apt install sysstat

选项

-t : 显示指定进程所关联的线程

-p : 指定 进程 pid

示例

查看进程 12345 所关联的线程

sudo pidstat -t -p 12345

查看所有进程所关联的线程

sudo pidstat -t

查看进程 12345 所关联的线程,每隔 1 秒输出一次

sudo pidstat -t -p 12345 1

查看所有进程所关联的线程,每隔 1 秒输出一次

sudo pidstat -t 1

top 命令

top 命令查看某一个进程下的线程,需要用到 -H 选项在结合 -p 指定 pid

选项

-H : 显示线程信息

-p : 指定 进程 pid

示例

查看进程 12345 所关联的线程

sudo top -H -p 12345

查看所有进程所关联的线程

sudo top -H

ps 命令

ps 命令结合 -T 选项就可以查看某个进程下所有线程

选项

-T : 显示线程信息

-p : 指定 进程 pid

示例

查看进程 12345 所关联的线程

sudo ps -T -p 12345

查看所有进程所关联的线程

sudo ps -T

常见的并发方案

1. 多进程模式

多进程模式下,每个进程负责不同的任务,互不干扰,各自运行在不同的内存空间,互不影响。

  • 优点:
    • 进程的地址空间独立,一旦某个进程出现异常,不会影响其他进程
  • 缺点:
    • 每个进程都需要分配独立的内存空间,创建进程的代价高,占用更多的内存
    • 进程间协同,进程间通讯比较复杂
  • 适用场景:
    • 多个任务联系不是非常紧密,可以采用多进程模式
    • 任务之间没有依赖关系,可以采用多进程模式

2. 多线程模式

多线程模式下,一个进程内可以有多个线程,共享同一份内存空间,线程之间可以直接通信。

  • 优点:
    • 线程间通信简单
    • 同一个进程的多个线程可以共享资源,可以提高资源利用率
  • 缺点:
    • 线程没有独立的进程地址空间,主线程退出后,其他线程也会退出
    • 线程切换和调度需要消耗资源,线程数量过多,会消耗系统资源
    • 线程间同步复杂,需要考虑线程安全问题
  • 适用场景:
    • 任务之间有依赖关系,可以采用多线程模式
    • 任务之间通信比较频繁,可以采用多线程模式

创建线程

1. pthread_create()

pthread_create() 用来创建线程,创建成功后,线程就开始运行,
pthread_create() 调用成功后,会返回 0,否则返回错误码。

函数头文件:

#include 

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);

参数说明:

  • thread: 指向 pthread_t 类型的指针,用来存储线程的 ID。
  • attr: 线程属性,可以为 NULL,表示使用默认属性。
  • start_routine: 线程的入口函数.
  • arg: 传递给线程入口函数的参数。

返回值:

  • 0: 创建成功。
  • EAGAIN: 资源不足,创建线程失败。
  • EINVAL: 参数无效。
  • ENOMEM: 内存不足,创建线程失败。

注意:

  • 一旦子线程创建成功,则会被独立调度执行,并且与其他线程 并发执行
  • 在编译时需要链接 -lpthread 库。

示例:创建一个线程

// todo : 创建一个线程,并在线程中打印出“Hello, World!”
#include 
#include 
#include 

// 线程函数
//@param arg 线程函数参数
void * print_hello(void *arg) {
   
    printf("%s\n",(char *)arg);
}

int main() {
   
    pthread_t tid; //? typedef unsigned long int pthread_t;
    // 创建线程
    //@param tid 线程ID
    //@param attr 线程属性
    //@param start_routine 线程函数
    //@param arg 线程函数参数
    int ret = pthread_create(&tid, NULL,print_hello, "Hello, World!");
    if (ret!= 0){
   
        printf("pthread_create error!\n");
        return 1;
    }
    sleep(1); // 等待线程执行完毕
    return 0;
}

2. pthread_exit() 退出线程

pthread_exit() 用来退出线程,线程执行完毕后,会自动调用 pthread_exit() 退出。

函数头文件:

#include 

void pthread_exit(void *retval);

参数说明:

  • retval: 线程退出时返回的值。
  • 线程函数执行完毕后,会自动调用 pthread_exit() 退出。

3. pthread_join() 等待线程结束

pthread_join() 用来等待线程结束,
调用 pthread_join() 后,当前线程会被阻塞,直到线程结束。

函数头文件:

#include 

int pthread_join(pthread_t thread, void **retval);

参数说明:

  • thread: 线程 ID。
  • retval: 指向线程返回值的指针,用来存储线程退出时返回的值。(二级指针)

返回值:

  • 0: 等待成功。
  • EINVAL: 参数无效。
  • ESRCH: 线程 ID 不存在。
  • EDEADLK: 线程处于死锁状态。

示例:

// todo : 创建一个线程,并在线程中打印出“Hello, World!”
#include 
#include 
#include 

// 线程函数
//@param arg 线程函数参数
void * print_hello(void *arg) {
   
    sleep(1); // 休眠1秒
    printf("%s\n",(char *)arg);
    pthread_exit(NULL); // 线程退出
}

int main() {
   
    pthread_t tid; //? typedef unsigned long int pthread_t;
    // 创建线程
    //* @param tid 线程ID
    //* @param attr 线程属性
    //* @param start_routine 线程函数
    //* @param arg 线程函数参数
    int ret = pthread_create(&tid, NULL,print_hello, "Hello, World!");
    if (ret!= 0){
   
        printf("pthread_create error!\n");
        return 1;
    }

    printf("等待线程结束...\n");
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    pthread_join(tid, NULL);

    return 0;
}
等待线程结束...
Hello, World!

线程分离

线程分为可结合的与可分离的

  • 可结合
    • 可结合的线程能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源(如栈)是不释放的。
    • 线程创建的默认状态为 可结合的,可以由其他线程调用 pthread_join 函数等待子线程退出并释放相关资源
  • 可分离
    • 不能被其他线程回收或者杀死的,该线程的资源在它终止时由系统来释放。
 
 

你可能感兴趣的:(网络编程,c/c++,c语言,linux,c,多线程,多进程,c高级)