• Day 54 多线程


    1. 多线程

    1.1 线程概念

            使用多线程的,可以减少操作系统的开支。如果我们为了一个任务开一个新的进程的话,Liunx会分配给它独立的地址空间,建立很多数据表来维护它的代码段、堆栈段和数据段。而我们如果是运行一个进程的多个线程,进程彼此之间会使用相同的地址空间,共享大部分数据,而启动一个线程花费的空间要远远小于启动一个进程所花费的空间,并且一个进程间的线程切换所花费的时间也远远小于进程间切换的时间。

            对于不同进程来说,他们所具有的是独立的数据空间,进行数据的传递会很费时,然而线程间有着方便的通信机制,由于同一个的进程下的线程之间空间是共享的,所以一个线程的数据可以直接为其他线程所用;当然,再用线程间数据共享的时候也会有些问题:有些变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程带来毁灭性的打击,这也是我们编写多线程程序的时候最需要注意的地方。

    1.1.1 什么是线程

    lwp:light weight process 轻量级的进程,本质仍是进程(在Linux环境下)

    进程:有独立PCB,且有独立地址空间

    线程:有独立PCB,但没有独立的地址空间

    区别:在于是否共享地址空间

    独居(进程)        合租(线程)

     

    Linux下:

            线程:最小的执行单位

            进程:最小的分配资源单位(可以看成是一个只有一个线程的进程) 

     

    命令:ps -Lf 进程id       //查询进程信息        LWP是线程号

     

     1.1.2 Liunx内核实现线程原理

            在类Unix系统中,借助进程机制提出了线程的概念,因为在此类系统中,进程和线程的关系密切

    1.轻量级进程(ight-weight process),也有PCB,创建线程使用的底层函数和进程一样, 都是clone

    2.从内核里看进程和线程是一样的, 都有各自不同的PCB,但是线程PCB中指向内存资源的三级页表是 相同的。

    3.进程可以蜕变成线程。

    4.线程可看做寄存器和栈的集合。

    5.在linux下,线程最是小的执行单位:进程是最小的分配资源单位。

    三级映射:

            进程PCB--->页目录(可以看成是数组,首地址位于PCB中)---->页表---->物理页面---->内存单元

            对于一个进程来说,相同的地址(同一个虚拟地址)在不同的进程中,反复使用而不冲突,原因是他们虽然虚拟地址一样,但页目录、页表、物理页面各不相同。相同的虚拟地址,映射到不同的物理页面内存单元,最终访问不同的物理页面

            但是线程不同,两个线程拥有各自独立的PCB,但共享一个页目录,也就共享同一个页表和物理页面。所以两个PCB共享一个地址空间

            实际上,无论是创建进程的fork,还是创建线程的pthread_create,底层的实现都是调用同一个内核函数clone

            如果复制对方的地址空间,就产出一个进程,如果共享对方的地址空间,就产生一个线程

            因此Linux内核是不区分进程和线程的,只是在用户层面上进行区分,所以线程所有操作函数是pthread库函数,而非系统调用

            Linux系统下的多线程遵循POSIX线程接口,成为pthread,编写Linux下的都县城程序,需要使用头文件pthread.h

    1.1.3 线程共享资源

    1. 文件描述符表

    2. 每种信号的处理方式

    3. 当前工作目录 

    4. 用户ID和组ID

    5. 内存地址空间(.text/.data/.bss/heap/共享库)、共享全局变量

    1.1.4 线程非共享资源

    1. 线程id

    2. 处理器现场和栈指针(内核栈)

    3. 独立的栈空间(用户空间栈)

    4. errno变量

    5. 信号屏蔽字

    6. 调度优先级

    1.1.5 线程优、缺点

    优点:

            1. 提高程序并发性

            2. 开销小

            3. 数据通信、共享数据方便

    缺点:

            1. 属于库函数、不稳定

            2. 调试,编写困难,gdb不支持

            3. 对信号支持不好

    优点相对突出,缺点均不是硬伤。Linux 下由于实现方法导致进程、线程差别不是很大

    温馨提示:有些函数手册没有,按照命令安装:sudo apt install manpages-posix-dev

    1.2 线程控制原语

    1.2.1 pthread_self函数

    获取当前程序的线程ID

    1. pthread_t pthread_self(void);
    2. 返回值:
    3. 成功:调用的线程ID
    4. 失败:无

    线程ID:pthread_t类型,本质是在Linux下为无符号整数(%lu),其他系统中可能是结构体实现

                    线程ID是进程内部识别标识(两个不同进程,线程ID允许相同)

    注意:不应使用全局变量pthread_t tid,在程序中通过pthread_create传出的参数是新创建的线程ID,而应该使用pthread_self

    例:获取线程ID

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. int main()
    10. {
    11. pthread_t tid;
    12. tid = pthread_self();
    13. printf("main() pid = %d, tid = %lu\n", getpid(), tid);
    14. while(1);
    15. return 0;
    16. }

     

    1.2.2 pthread_create函数

    创建一个新线程,其作用,对应进程中的fork函数

    1. int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start routine)
    2. (void *), void *arg)l
    3. 返回值:
    4. 成功0
    5. 失败错误号 Liunx环境下,所有线程特点,失败均返回错误号
    6. 参数:
    7. 1.传出参数,保存系统为我们分配好的新进程ID
    8. 2.线程属性,通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改参数
    9. 3.回调子线程函数名
    10. 4.参3子线程函数需要的参数,没的话传NULL

     注意:编译命令增加参数 -lpthread

    例:创建子线程,各自循环打印信息

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. void sys_err(const char *str)
    10. {
    11. perror(str);
    12. exit(1);
    13. }
    14. void *tfn(void *arg)
    15. {
    16. printf("thread: pid=%d, tid=%lu\n",getpid(),pthread_self());
    17. int i=0;
    18. while(i<10)
    19. {
    20. printf("thread----tfn()--do count=%d\n",i);
    21. sleep(1);
    22. i++;
    23. }
    24. //while(1);
    25. return NULL;
    26. }
    27. int main()
    28. {
    29. pthread_t tid;
    30. tid=pthread_self();
    31. printf("main() pid=%d ,tid=%lu\n",getpid(),tid);
    32. //ptread_create()创建线程
    33. //参1:传出 一个变量,这个变量用于保存新创建的线程id
    34. //参2:传入 *attr所指向的内容不能修改,或是传入常量
    35. //参3:传入 回调函数地址,函数名就是地址,所有传函数名
    36. //参4:传入 回调函数需要的参数
    37. //返回值:0代表成功,失败返回错误码
    38. int ret=pthread_create(&tid,NULL,tfn,NULL);
    39. if(ret!=0)
    40. {
    41. sys_err("pthread_create err");
    42. }
    43. printf("------main() thread: pid=%d, tid=%lu\n",getpid(),pthread_self());
    44. int i=0;
    45. while(i<15)
    46. {
    47. printf("thread----main()----count=%d\n",i);
    48. sleep(1);
    49. i++;
    50. }
    51. return 0;
    52. }

     例程:循环创建N个子线程,每个线程打印自己是第几个被创建的线程。(类似于进程循环创建子进程)   

    1. #include
    2. #include
    3. #include
    4. #include
    5. void *tfn(void *arg)
    6. {
    7. int i;
    8. i = (int)arg;
    9. sleep(i); //通过i来区别每个线程
    10. printf("I'm %dth thread, Thread_ID = %lu\n", i+1, pthread_self());
    11. return NULL;
    12. }
    13. int main(int argc, char *argv[])
    14. {
    15. int n = 5, i;
    16. pthread_t tid;
    17. if (argc == 2)
    18. n = atoi(argv[1]);
    19. for (i = 0; i < n; i++) {
    20. pthread_create(&tid, NULL, tfn, (void *)i);
    21. //将i转换为指针,在tfn中再强转回整形。
    22. }
    23. sleep(n);
    24. printf("I am main, and I am not a process, I'm a thread!\n"
    25. "main_thread_ID = %lu\n", pthread_self());
    26. pthread_exit(NULL);
    27. }

  • 相关阅读:
    Git基础操作
    面试官:如何提升应用的 Lighthouse 分数
    内网开发新项目之流程记录
    超简单免费转换ape到flac
    SLAM运动模型
    Yii 知识点总结
    zemax光线光扇图
    软件测试肖sir__python之ui自动化实战和讲解(03)
    drools规则引擎并发结果不准确问题记录
    并行前缀和计算——MPI SCAN算法的C语言实现
  • 原文地址:https://blog.csdn.net/AppreciateIt/article/details/127554506