• Linux Day14 :线程的创建与同步


    一、简单认知

    进程:一个正在运行的程序

    线程:进程内部的一个执行路径

    头文件:#include<pthread.h>

    二、进程与线程的区别

    进程是资源分配的最小单位,线程是 CPU 调度的最小单位
    进程有自己的独立地址空间,线程共享进程中的地址空间
    进程的创建消耗资源大,线程的创建相对较小
    进程的切换开销大,线程的切换开销相对较小

    三、线程的实现方式

    用户级:开销小,可以创建很多,但不能利用多处理器资源。就算你弄了多了线程,但在内核中你只有一个线程,尽管你有多个处理器,还是通过时间片轮转法来实现并发

    内核级:开销大(相对用户级来说),由内核直接管理,可以利用多处理器资源。Linux采用的就是这种

    组合:介于这三种之间

    在操作系统中,线程的实现有以下三种方式:

    ◼ 用户级线程 :用户级线程是完全在用户空间中实现的线程。操作系统内核对其一无所知,只知道进程的存在。用户级线程的创建、调度和管理完全由用户级的线程库完成。由于这些操作不需要内核的介入,所以用户级线程的创建和切换比内核级线程更加快速、高效。然而,由于操作系统对用户级线程一无所知,因此一个阻塞的用户级线程会导致整个进程阻塞,这是用户级线程的一个主要缺点。

    ◼ 内核级线程 :内核级线程是直接由操作系统内核支持和管理的线程。内核维护了所有内核线程的上下文信息,并负责线程的调度和切换。因此,内核级线程可以利用多处理器并行性,同时,当一个内核级线程阻塞时,内核可以调度该进程的其他线程执行。然而,内核级线程的创建和切换需要进行用户态到内核态的切换,因此成本比用户级线程高。

    ◼ 组合级线程:组合级线程是用户级线程和内核级线程的组合,试图结合两者的优点。在这种模型中,一个用户级线程对应于一个或多个内核级线程。这样,即使一个用户级线程阻塞,也不会阻塞整个进程,因为内核可以调度对应的其他内核级线程执行。同时,用户级线程的创建和切换可以在用户空间内完成,避免了频繁的用户态到内核态的切换。

    Linux 实现线程的机制非常独特。从内核的角度来说,它并没有线程这个概念。Linux 把
    所有的线程都当做进程来实现。内核并没有准备特别的调度算法或是定义特别的数据结构来
    表征线程。相反,线程仅仅被视为一个与其他进程共享某些资源的进程。每个线程都拥有唯
    一隶属于自己的 task_struct,所以在内核中,它看起来就像是一个普通的进程(只是线程和
    其他一些进程共享某些资源,如地址空间)。
    使用ps -eLf以上图为例,进程id都是5620,但是有6个线程,其线程id从5620开始到5631( 所有的线程都当做进程来实现)

    四、一些栗子

    1、第一个小栗子

    step1:不加睡眠函数

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. void* fun(void* arg){
    7. for(int i=0;i<5;i++)
    8. {
    9. printf("fun run\n");
    10. }
    11. }
    12. int main()
    13. {
    14. pthread_t id;
    15. pthread_create(&id,NULL,fun,NULL);
    16. for(int i=0;i<5;i++)
    17. {
    18. printf("main run\n");
    19. //sleep(1);
    20. }
    21. exit(0);
    22. }

    结果:

    一般是全是主函数,因为在调用线程函数的时候主函数已经执行完毕,Exit(0)了,所以不会打印fun函数

    step 2:主函数加上睡眠函数

    1. void* fun(void* arg){
    2. for(int i=0;i<5;i++)
    3. {
    4. printf("fun run\n");
    5. }
    6. }
    7. int main()
    8. {
    9. pthread_t id;
    10. pthread_create(&id,NULL,fun,NULL);
    11. for(int i=0;i<5;i++)
    12. {
    13. printf("main run\n");
    14. sleep(1);
    15. }
    16. exit(0);
    17. }

    2、第二个小栗子

    循环打印所在位置

    1. void*thread_fun(void*arg)
    2. {
    3. int*p=(int*)arg;
    4. int index=*p;
    5. for(int i=0;i<3;i++)
    6. {
    7. printf("intdex=%d\n",index);
    8. sleep(1);
    9. }
    10. }
    11. int main()
    12. {
    13. pthread_t id[5];
    14. for(int i=0;i<5;i++)
    15. {
    16. pthread_create(&id[i],NULL,thread_fun,(void*)&i);
    17. }
    18. for(int i=0;i<5;i++){
    19. pthread_join(&id[i],NULL);
    20. }
    21. }

    结果

    问题:

    这里将i的地址传给了fun()参数,解引用后就能得到i的值,这在单线程(一个内核)是没得问题的,但是多核多线程是有的,每个线程同时获取i的地址,但是线程启用是需要花费时间,等打印获取i的值时,那一刻的i不一定是你传入时期的i.

    3.第三个小例子

    将一个全局变量加到5000

    1. #include
    2. int g_val=1;
    3. void*fun(void* arg)
    4. {
    5. for(int i=0;i<1000;i++)
    6. {
    7. printf("val=%d\n",g_val++);
    8. }
    9. }
    10. int main()
    11. {
    12. pthread_t id[5];
    13. int i=0;
    14. for(;i<5;i++)
    15. {
    16. pthread_create(&id[i],NULL,fun,NULL);
    17. }
    18. for(i=0;i<5;i++)
    19. {
    20. pthread_join(id[i],NULL);
    21. }
    22. exit(0);
    23. }

    结果不到5000,如果只有一个处理器的话不会出现两个程序并行的情况,但是处理器大于2时,出现两个程序并行。

    解决方案:

    1.加信号量

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. int g_val=1;
    8. sem_t sem;
    9. void*fun(void* arg)
    10. {
    11. for(int i=0;i<1000;i++)
    12. {
    13. sem_wait(&sem);
    14. printf("val=%d\n",g_val++);
    15. sem_post(&sem);
    16. }
    17. }
    18. int main()
    19. {
    20. pthread_t id[5];
    21. sem_init(&sem,0,1);
    22. int i=0;
    23. for(;i<5;i++)
    24. {
    25. pthread_create(&id[i],NULL,fun,NULL);
    26. }
    27. for(i=0;i<5;i++)
    28. {
    29. pthread_join(id[i],NULL);
    30. }
    31. sem_destroy(&sem);
    32. exit(0);
    33. }

    2.加互斥锁

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. int g_val=1;
    8. pthread_mutex_t mutex;
    9. void*fun(void* arg)
    10. {
    11. for(int i=0;i<1000;i++)
    12. {
    13. pthread_mutex_lock(&mutex);
    14. printf("val=%d\n",g_val++);
    15. pthread_mutex_unlock(&mutex);
    16. }
    17. }
    18. int main()
    19. {
    20. pthread_t id[5];
    21. pthread_mutex_init(&mutex,NULL);
    22. int i=0;
    23. for(;i<5;i++)
    24. {
    25. pthread_create(&id[i],NULL,fun,NULL);
    26. }
    27. for(i=0;i<5;i++)
    28. {
    29. pthread_join(id[i],NULL);
    30. }
    31. pthread_mutex_destroy(&mutex);
    32. exit(0);
    33. }

    留一下一道思考题轮流打印abcabc按照这个顺序,该如何处理

  • 相关阅读:
    聊聊推荐系统的评测(上)
    FlashAttention2原理解析以及面向AIGC的加速实践
    Houdini点云学习-移除稀疏/聚集的点
    LabVIEW步进电机的串口控制方法与实现
    docker-compose安装nacos
    Paraverse平行云LarkXR:支持SR/VR/AR/MR多种客户端接入的全栈实时云渲染解决方案
    【一起学Rust】Rust学习前准备——注释和格式化输出
    笔记(上):mysql-DuplicateUpdate和java的threadpool的“死锁“
    大数据-玩转数据-Flume
    详解Nacos 配置中心客户端配置缓存动态更新的源码实现
  • 原文地址:https://blog.csdn.net/hello_world_juns/article/details/132874693