操作系统—线程(1)

一.线程概念

  • 定义:线程是程序执行的最小单位,是轻量级进程,通常用于在一个进程中并行执行多个任务。
  • 资源分配:进程是系统中最小的资源分配单位,而线程则是系统中最小的执行单位。

1.线程的优点

  • 资源节省:与多进程相比,线程因为共享同一进程的资源而更节省资源。
  • 共享变量:线程间可以容易地共享变量,因为它们共享相同的内存空间。

2.线程的特征

  1. 共享资源:线程可以访问进程中的所有资源,包括数据段、堆等。
  2. 效率:线程的创建和切换比进程更高效,通常可以快约 30%。
  3. 三方库支持
    • 需要包含头文件 pthread.h
    • 编译时需要链接 -pthread 库,如 libpthread.so

3.线程的缺点

  1. 稳定性:线程由于共享地址空间,一旦发生错误可能影响整个进程的稳定性。
  2. 调试难度:多线程程序的调试相对复杂,需要跟踪线程的执行和同步状态。

4.线程与进程的区别

  • 资源
    • 线程可以共享同一进程的资源,如内存、文件描述符等。
    • 线程也有自己的私有资源,如栈和线程局部存储。
    • 进程间资源是独立的,通常需要 IPC(进程间通信)机制来交换数据。
  • 空间
    • 进程拥有独立的地址空间,进程间通信需要特定的 IPC 机制。
    • 线程共享进程的地址空间,可以直接访问共享数据,通信更为直接和快速。

5.调试多线程程序

  • 使用调试器(如 gdb)时,可以使用 info threads 查看线程列表。
  • 使用 thread <thread number> 命令可以切换到特定线程的上下文进行调试。

二.线程设计框架POSIX

1.创建多线程 

  • 函数原型int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
    • thread:线程 ID,需要实现定义并由函数返回。
    • attr:线程属性,通常为 NULL,表示默认属性。
    • start_routine:指向指针函数的函数指针,本质上是一个函数的名称即可,称为回调函数,是线程的执行空间。
    • arg:回调函数的参数。
  • 返回值:成功返回 0,失败返回错误码。
  • 注意:1)一次pthread_create执行只能创建一个线程。
               2)每个进程至少有一个线程称为主线程。
               3)主线程退出则所有创建的子线程都退出。 
               4)主线程必须有子线程同时运行才算多线程程序。
               5)线程id是线程的唯一标识,是CPU维护的一组数字。
               6)pstree 查看系统中多线程的对应关系。
               7)多个子线程可以执行同一回调函数。

2.当前线程 ID 获取

  • 函数原型pthread_t pthread_self(void);
    • 参数:无。
    • 返回值:成功:返回当前线程的 ID,unsigned long int; %lu。失败则返回-1。

3.线程退出

  • 自行退出void pthread_exit(void *retval);
    • retval:线程退出时的返回状态。
    • 返回值:无。
  • 强制退出int pthread_cancel(pthread_t thread);
    • thread:请求结束一个线程tid。
    • 返回值:成功为0,失败为-1。

4.线程资源回收

  • 函数原型int pthread_join(pthread_t thread, void **retval);
    • thread:要回收的子线程的 ID。
    • retval:子线程的返回值/状态。--->ptread_exit(值);
  • 线程回收机制:1)不同于进程,没有孤儿线程和僵尸线程。
                             2)主线程结束任意生成的子线程都会结束。
                             3)子线程的结束不会影响到主线程的运行。
  • 返回值:成功返回 0,失败返回 -1。
  • 子线程的回收策略:1)如果预估子线程可以有限范围内结束则正常用pthread_join等待回收。
                                    2)如果预估子线程可能休眠或者阻塞,则等待一定时间后强制回收。
                                    3)如果子线程已知必须长时间运行则,不再回收其资源。
void *result = NULL;
int status = pthread_join(tid, &result);
if (status == 0 && result != NULL) {
    // 使用返回值
    printf("Thread returned: %ld\n", (long)result);
}

5.退出回收返回值原理

  • 子线程使用 pthread_exit 返回一个 void* 类型的指针。
  • 主线程通过 pthread_join 获取子线程的返回指针,并访问指向的数据。
  • 子线程退出的时候,可以返回一个内存地址,改值所在的内存中可以存储任何数据,只要地址存在,则数据都可以正常返回。
     地址类型
  1. 栈区变量:错误。子线程结束后,其栈帧被销毁,返回的地址不再有效。
  2. 全局变量:可行。全局变量在整个程序的生命周期内有效,可以直接访问。
  3. 静态变量:可行。静态变量在函数调用之间保持其值。
  4. 堆区变量:可行。动态分配的内存在未显式释放前一直有效。

      主线程通过一个地址形式的变量来接受子进程
      返回的地址变量就可以将该地址中的数据取到。

    示例:子线程返回堆区整数
  • 目的:从子线程返回一个动态分配的整数。
  • 步骤
    1. 在子线程中使用 malloc 分配内存并存储整数。
    2. 使用 pthread_exit 返回指向这块内存的指针。
    3. 在主线程中使用 pthread_join 获取指针并读取整数。
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

void* thread_function() {
    int* data = (int*)malloc(sizeof(int));
    *data = 10; // 将数据存储在堆区
    pthread_exit(data); // 返回指向数据的指针
}

int main() {
    pthread_t tid;
    void** result = NULL; // 注意这里使用 void** 来接收地址

    // 创建线程
    if (pthread_create(&tid, NULL, thread_function, NULL) != 0) {
        perror("Failed to create thread");
        return 1;
    }

    // 等待线程结束并获取返回值
    if (pthread_join(tid, (void**)&result) == 0 && result != NULL) {
        printf("Thread returned: %d\n", *(int*)(*result));
        free(*result); // 释放子线程分配的内存
    } else {
        perror("Failed to join thread or no result");
    }

    return 0;
}

三.线程参数传递

       使用 pthread_create 传递参数给线程函数。

  • 传整数:
//线程函数
void* add(void* arg) {
    // 强制转换参数
    int a = *((int *)arg);
    // 这里可以添加更多的逻辑
    return NULL;
}

//线程创建(写到需要的函数体中)
pthread_t tid;
int x = 2, y = 3;
pthread_create(&tid, NULL, add, (void *)&x);


  • 传字符:通常作为整数类型传递。
  • 传字符串
    • 栈区字符数组:不应直接传递,因为数组会退化成指针。
    • 字符串常量:可以直接传递字符串指针。
    • 堆区字符串:动态分配内存后传递指针,使用 malloc 分配,记得 free
//线程函数
void* printString(void* arg) {
    char* str = (char *)arg;
    printf("%s\n", str);
    return NULL;
}

//线程创建
char *str = "Hello, thread!";
char *pc = (char *)malloc(128);//堆区字符串
pthread_create(&tid, NULL, printString, (void *)str);
  • 传结构体:1)定义结构体类型。
                      2)用结构体定义变量。
                      3)向pthread_create传结构体变量。
                      4)从fun子线程中获取结构体数据。
//线程函数
void* processStruct(void* arg) {
    MyStruct* myData = (MyStruct *)arg;
    // 使用 myData->a 和 myData->b
    return NULL;
}

//线程创建
MyStruct data = {10, 20};
pthread_create(&tid, NULL, processStruct, (void *)&data);

四.线程属性

1.初始化

  • 结构int pthread_attr_init(pthread_attr_t *attr);
  • 功能:初始化一个attr的变量。
  • 参数:attr,需要变量来接受初始值。
  • 返回值:成功为0,失败为非0。
    • detachstate:设置为 PTHREAD_CREATE_DETACHED

3.销毁

  • 结构:int pthread_attr_destroy(pthread_attr_t *attr);
  • 功能:销毁attr变量。
  • 参数:attr 属性变量。
  • 返回值:成功为0,失败为非0。

4.分离属性设置

  • 结构:int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
  • 功能:把一个线程设置成相应的属性。
  • 参数1:attr 属性变量,有init函数初始化他。
  • 参数2:detach state:有两个可选值:

                    PTHREAD_CREATE_DETACHED:设置分离属性。
    
                   第二种设置分离属性:
                   int pthread_deatch(pthread_t thread);
                   功能:设置分离属性
                   参数:线程id号,填自己的id

  • 返回值:成功为0,失败为非0。

五.线程清理

1.注册清理函数

  • 结构:void pthread_cleanup_push(void (*routine)(void *), void *arg);
  • 功能:注册一个线程清理函数。
  • 参数:routine,线程清理函数的入口
               arg,清理函数的参数。
  • 返回值:无

2.调用清理函数

  • 结构:void pthread_cleanup_pop(int execute);
  • 功能:调用线程清理函数。
  • 参数:execute:非0  执行清理函数
                                0 ,不执行清理
  • 返回值:无。

六.编译和调试

  • 编译时加载库:-lpthread

    第一个方法重启后失效:
    alias gcc='gcc -g -pthread '
    unalias gcc 

    第二个方法永久起作用:
    cd ~ //家目录
    vim .bashrc
    alias gcc='gcc -g -pthread '  :wq

    source .bashrc  生效

  • 调试多线程程序时使用 info thread 命令查看线程信息。

七.命令行工具

1.pstree

  • 用途pstree 显示当前运行的进程树。它以树状图的形式显示进程及其子进程,包括线程。
  • 用法
    • 运行 pstree 可以看到所有进程的树状视图。
    • pstree -p 还会显示进程的 PID。
    • pstree -T 显示线程而不是进程。

2.ps

  • 用途ps 列出当前系统的进程状态。
  • 用法
    • ps -e 显示所有进程。
    • ps -L 显示轻量级(线程)信息。
   1)查看线程相关信息
  • ps -eLf:这个命令列出所有进程,并显示每个进程的线程信息。
    • e 表示显示所有进程。
    • L 表示显示线程信息。
    • f 表示完整格式,显示更多详细信息。
  • 这个命令会显示进程的 PID、父进程的 PID、线程 ID (lwp)、状态 (stat) 和命令名称 (comm)。
   2)查看特定线程信息
  • ps -eLo pid,ppid,lwp,stat,comm:这个命令列出所有进程,并允许你选择性地显示特定列的信息。
    • o 后面跟的是自定义的输出格式,这里指定了要显示的列:进程 ID (pid)、父进程 ID (ppid)、线程 ID (lwp)、状态 (stat) 和命令名称 (comm)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值