Linux线程

  • 线程概念

    在传统操作系统中,进程就是一个运行中程序的描述信息–PCB 控制程序的运行

    Linux系统并没有对线程设计一个TCB来控制线程的运行

    在Linux下,线程以进程PCB模拟实现,也就是说在Linux下PCB其实是一个线程

    在Linux下,线程其实是一个轻量级进程

    Linux进程实际是一个线程组 当中包含一个或者多个线程

    因为CPU调度程序运行时调度PCB,因此线程是CPU调度的最小单位

    因为一个程序运行会分配资源给线程组, 所以进程是资源分配的最小单位

  • 多进程和多线程优缺点对比

    • 线程优缺点

      • 都可以并发/并行处理任务,提高处理效率

      • 对临界资源操作需要考虑更多, 编程更加复杂

      • 一个进程中的线程共用同一个虚拟地址空间

        vfork()创建一个子进程共用一个虚拟地址空间,怕出现调用栈混乱,因此子进程运行完毕或程序替换后父进程才开始运行

        多线程PCB使用同一个虚拟地址空间,如何实现同时运行而不会出现调用栈混乱

        为每个线程在虚拟地址空间单独分配一块空间

      • 优点

        • 线程间通信更加方便
        • 线程的创建销毁成本更低
        • 线程间切换调度成本更低
        • 线程的执行粒度更细
      • 缺点

        • 线程之间缺乏访问控制, 健壮性低
        • 系统调用(exit) 异常针对的是整个进程产生效果
    • 进程优缺点

      • cpu密集型程序

        程序中都是大量运算操作

      • io密集型程序

        程序中都是大量io操作

  • 线程控制

    线程创建、线程终止、线程等待、线程分离

    创建线程是一个用户态线程,在内核中对应了一个轻量级进程实现程序的调度运行

  • 同一个进程的线程之间独有的数据

    • 寄存器
    • errno
    • 信号屏蔽字
    • 线程标识符
  • 同一个进程的线程之间共享数据

    • 数据段。代码段

    • 文件描述符表

    • 信号的处理方式

  • 线程创建

    int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
    参数
        thread:返回线程ID
        attr:设置线程的属性,attr为NULL表示使用默认属性
        start_routine:是个函数地址,线程启动后要执行的函数
        arg:传给线程启动函数的参数
        返回值:成功返回0;失败返回错误码
    

    注意: pthread函数出错时不会设置全局变量errno(而大部分POSIX函数会这样做)。而是将错误码通过返回值返回。pthread同样提供线程内的errno变量,以支持其它使用errno的代码。对于pthread函数的错误,建议通过返回值判定,因为读取返回值要比读取线程中的errno变量开销小

  • 进程ID和线程ID

    • 在Linux中,目前线程是Native POSIX Thread Libaray,简称NPTL。在这种实现下, 线程又被称为轻量级进程,每一个用户态的线程,在内核中都对应一个调度实体,也拥有自己的进程描述符(task_struct)结构体

    • 没有线程之前,一个进程对应内核里的一个进程描述符,对应一个进程ID,但是引入线程后,一个用户进程下管辖N个用户态线程,每个线程作为一个独立的调度实体在内核态都有自己的进程描述符,进程和内核的描述符一下子就编程1:N关系,POSIX标准又要求进程内的所有线程调用gitpid函数时返回相同的进程ID

    • Linux内核引入线程组的概念

      多线程的进程,又被称为线程组,线程组内每个线程在内核之中都存在一个进程描述符(task_struct)与之对应。进程描述符结构中的pid,表面上看对应的是进程ID,其实不然,对应的是线程ID;进程描述符中的tgid,含义是Thread Group ID,该值对应的是用户层面的进程ID

      用户态系统调用内核进程描述符中对应的结构
      线程IDpid_t gettid(void)pid_t pid
      进程IDpid_t getpid(void)pid_t tgid

      查看线程ID:

      在这里插入图片描述

      • ps命令中的-L选项, 会显示:

        1. LWP:线程ID, 即gettid()系统调用的返回值
        2. NLWP:线程组内线程的个数
      • 线程组ID总是和主线程的线程组ID一致,无论是主线程直接创建线程,还是创建出来的线程再次创建线程,都是这样。

    • 线程ID及进程地址空间

      pthread_create函数返回的线程ID,是供给用户调度的与进程调度的线程ID不同

      pthread_create函数第一个参数指向一个虚拟内存单元,该内存单元地址即为新创建线程的线程ID。

  • 获取线程id

    pthread_t pthread_self(void)
    // 获取当前线程id
    

    pthread_t 类型:在Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质是一个进程地址空间上的一个地址。

  • 线程终止

    • 线程入口函数中return;main中不能return,否则退出的是进程

    • pthread_exit(void* retval) 主动退出,谁调用谁退出

    • pthread_cance(tid) 被动退出,取消指定的线程

    #include <stdio.h>
    #include <pthread.h>
    #include <stdlib.h>
    #include <string.h>
    
    void* thr_start(void *arg)
    {
        // printf("thread start!\n");
        // sleep(5);
        // pthread_exit("i did exit");
        while (1)
        {
            printf("i am new thread![%s][%d]\n", arg, (int)pthread_self());
            sleep(1);
        }
        // int* p = (int*)malloc(sizeof(int));
        // *p = 1;
        // return (void*)p;
    }
    
    int main()
    {
        pthread_t tid;
        int ret = pthread_create(&tid, NULL, thr_start, (void*)"for new");
        if (ret != 0)
        {
            fprintf(stderr, "thread create error: [$s]\n", strerror(ret));
            return -1;
        }
    
        // char exit[200] = {0}; // 不能使用数组形式的char数组
        // 如果线程是pthread_exit()退出, pthread_join接受的是pthread_exit函数中参数值
        // char* exit = NULL;
        // pthread_join(tid, (void**)&exit);
        // printf("exit info: %s\n", exit);
        // 如果线程是return退出,pthread_join接受的是return的值
        void* exit;
        // pthread_join(tid, &exit);
        // printf("exit info: %d\n", *(int*)exit);
        // 如果线程是通过pthread_cancel 退出,pthread_join接受的是PTHREAD_CANCELED
        sleep(5);
        pthread_cancel(tid);
        pthread_join(tid, &exit);
        if (exit == PTHREAD_CANCELED)
        {
            printf("exit info: PTHREAD_CANCELED\n");
        } else
        {
            printf("exit info: NULL");
        }
        
        
        while (1)
        {
            printf("i am main thread[%d]\n", (int)pthread_self());
            sleep(1);
        }
        return 0;
    }
    

    线程退出,也会为了保存退出返回值,而成为僵尸线程

    主线程退出,进程不会退出

  • 线程等待

    获取指定退出线程的返回值,并且允许操作系统回收线程资源

    注意: 一个线程启动之后,默认有一个属性是线程处于joinable状态;

    处于joinable状态的线程, 退出后,不会自动释放资源,需要被其他线程等待

    int pthread_join(pthread_t thread, void** retval)
    	thread:  指定的线程ID
    	retval:  用于获取线程退出返回值
    	return: 成功返回0 失败非0  errno
    	功能: 等待线程退出,获取返回值,回收线程资源
    	前提,这个被等待的线程必须处于joinable
    
  • 线程分离

    默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join, 否则无法释放资源,导致资源泄露

    如果不关心线程的返回值,join就会是一种负担。所以就需要分离一个线程(设置线程的属性从joinable变为detach),线程退出后系统将自动回收资源,被分离的线程无法被等待

    若是非要pthread_join则会直接报错返回

    #include <pthread.h>
    
    int pthread_detach(pthread_t thread)
     thread: 指定的线程ID
     return 成功返回0 失败非0
    
  • 线程属性

    处于joinable状态的线程退出后不会自动释放资源需要被等待

    处于detach状态的线程退出后自动回收资源,不需要被等待

    线程默认的属性是joinable

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值