• Linux系统编程系列之线程


     Linux系统编程系列(16篇管饱,吃货都投降了!)

            1、Linux系统编程系列之进程基础

            2、Linux系统编程系列之进程间通信(IPC)-信号

            3、Linux系统编程系列之进程间通信(IPC)-管道

            4、Linux系统编程系列之进程间通信-IPC对象

            5、Linux系统编程系列之进程间通信-消息队列

            6、Linux系统编程系列之进程间通信-共享内存

            7、Linux系统编程系列之进程间通信-信号量组

            8、Linux系统编程系列之守护进程

            9、Linux系统编程系列之线程

            10、Linux系统编程系列之线程属性 

            11、Linux系统编程系列之互斥锁和读写锁

            12、Linux系统编程系列之线程的信号处理

            13、Linux系统编程系列之POSIX信号量

            14、Linux系统编程系列之条件变量

            15、Linux系统编程系列之死锁

            16、 Linux系统编程系列之线程池

    一、什么是线程

            线程(Thread)是计算机中的基本执行单元,是操作系统调度的最小单位。线程是进程内的一个独立执行流程,一个进程可以包含多个线程,这些线程共享进程的资源,但每个线程都有自己的独立栈空间以及程序计数器。

    二、线程与进程的优缺点

            1、线程的优点

            (1)、线程创建和销毁的开销比进程小,因为线程共享进程中的地址空间和其他资源。

            (2)、线程可以同时执行多个任务,提高了系统的并发性能。

            (3)、线程之间的通信和同步比进程之间的通信和同步更快捷和简单,因为线程共享同一进程的内存。

            (4)、线程可用于执行GUI等交互性任务而不会卡住整个应用程序。

            2、线程的缺点

            (1)、 多线程访问共享数据时,需要使用同步技术,否则会导致不可预期的结果。

            (2)、 线程的调试和bug定位比较困难,因为多个线程共享进程的执行环境。

            (3)、 如果线程中出现了异常,可能会影响整个进程。

            3、进程的优点

            (1)、 进程相互独立,不会相互影响,因此更加健壮和安全。

            (2)、进程可以在不同的硬件和操作系统上运行,更具有可移植性。

            (3)、 进程使用管道等IPC(进程间通信)机制可以方便的实现进程之间的通信和同步。

            4、进程的缺点

            (1)、进程创建和销毁的开销比较大,因为每个进程都需要独立的地址空间和系统资源。

            (2)、进程之间通信和同步需要使用IPC技术,比较繁琐和复杂。

            (3)、进程的并发性能比较差,不能同时执行多个任务。

    三、线程的使用场景

            1、多任务处理:多线程可以同时处理多个任务,提高程序的执行效率和响应速度。

            2、并发访问:当多个线程同时访问共享资源时,需要使用线程控制技术,避免出现竞态条件和死锁等问题。

            3、异步编程:线程可以在后台执行一些耗时的操作,不会阻塞主线程,提高程序的用户体验。

            4、服务器编程:服务器一般要同时处理多个客户端请求,使用多线程可以提高服务器的并发处理能力。

            5、图形界面编程:图形界面程序中需要使用线程避免阻塞用户界面,实现异步更新UI界面。

            6、大数据处理:对于大数据处理和分析,多线程可以提高数据处理的效率和速度。

            7、游戏开发:游戏开发中需要实时更新游戏画面和处理用户输入,需要使用多线程技术实现。

    四、与线程有关的函数API

            1、线程的创建

            创建一条POSIX线程非常简单,只需要指定线程的执行函数即可

    1. // 创建一条线程
    2. int pthread_create(pthread_t *thread,
    3. const pthread_attr_t *attr,
    4. void *(*start_routine) (void *),
    5. void *arg);
    6. // 接口说明:
    7. 返回值:成功返回0,失败返回一个错误码
    8. 参数thread:新线程的TID
    9. 参数attr:线程属性,若创建标准线程则该参数可设置为NULL
    10. 参数start_routine:线程函数,是一个回调函数,跟信号的例程函数有点像
    11. 参数arg:线程函数的参数

             2、线程的退出

            与进程类似,当一条线程执行完毕其任务时,可以使用接口来退出

    1. // 线程的退出
    2. void pthread_exit(void *retval);
    3. // 接口说明
    4. 参数retval:线程的返回值,若线程没有数据可返回则可写成NULL
    5. pthread_exit()exit()的区别
    6. pthread_exit():退出当前线程
    7. exit():退出当前进程(即退出进程中的所有进程)

             3、线程的结合

            与进程类似,线程退出后不会立即释放其所占有的系统资源,而会成为一个僵尸线程。其他线程可使用pthread_join()来释放僵尸线程的资源,并可获得其退出时返回的退出值,该函数接口被称为线程的接合函数:

    1. // 阻塞等待指定线程退出
    2. int pthread_join(pthread_t tid, void **val);
    3. // 非阻塞接合指定线程退出
    4. int pthread_tryjoin_np(pthread_t tid, void **retval);
    5. // 在指定时间内阻塞接合指定线程的退出
    6. int pthread_timedjoin_np(pthread_t tid, void **retval, const struct timespec *ashtime);
    7. // 接口说明
    8. 1)若指定tid的线程尚未退出,那么该函数将持续阻塞
    9. 2)若只想阻塞等待指定线程tid退出,而不想要其退出值,那么val可置为NULL
    10. 3)若指定tid的线程处于分离状态,或者不存在,则该函数会出错返回

            4、获取线程TID

            

    1. // 获取线程TID
    2. pthread_t pthread_self(void);
    3. // 接口说明
    4. 返回值:线程TID
    5. 该接口类似进程管理中的getpid(),但是进程的PID是系统全局资源,而线程的TID仅限于进程内部的线程间有效。当我们要对某条线程执行发送信号,取消,阻塞接合等操作时,需要用到线程的TID。

            5、线程错误码

            线程函数对系统错误码的处理跟标准C库函数的处理方式有很大不同,标准C库函数会对全局错误码errno进行设置,而线程函数发生错误时会直接返回错误码。以线程接合为例,若要判定接合是否成功,成功的情况下输出僵尸线程的退出值,失败的情况下输出失败的原因,实现代码应该这么写

    1. #include // 头文件中定义了errno变量
    2. void *val;
    3. errno = pthread_join(tid, &val);
    4. if(errno == 0)
    5. {
    6. printf("成功接合线程,其退出值为:%d\n", (int)val;
    7. }
    8. else
    9. {
    10. perror("接合线程失败");
    11. }

            6、函数单例

            许多时候,我们希望某个函数只被严格执行一次,这种需求在一些初始化功能模块中尤为常见,但是如果某个进程中内含多条线程,无法预先知晓哪条线程会先执行,那么初始化就会被执行多次,但如果使用函数单例就会只执行一次。

    1. // 函数单例启动接口
    2. int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));
    3. // 接口说明
    4. 参数once_control:用来关联某个函数单例,被关联的函数单例只会被执行一遍
    5. 参数init_routine:函数指针指向的函数就是只执行一遍的函数单例
    6. // 通常参数once_control指定为函数单例控制
    7. pthread_once_t once_control = PTHREAD_ONCE_INIT;

    五、案例

    1. // 线程的案例
    2. #include
    3. #include
    4. #include
    5. #include
    6. int flag = 0; // 简单的标志位来控制同步
    7. char data[100];
    8. // 线程1的例程函数,用来接收数据
    9. void *recv_routine(void *arg)
    10. {
    11. printf("I am recv_routine, my tid = %ld\n", pthread_self());
    12. while(1)
    13. {
    14. if(flag)
    15. {
    16. printf("pthread1 read data: %s\n", data);
    17. memset(data, 0, sizeof(data));
    18. flag = 0;
    19. }
    20. }
    21. }
    22. // 线程2的例程函数,用来发送数据
    23. void *send_routine(void *arg)
    24. {
    25. printf("I am send_routine, my tid = %ld\n", pthread_self());
    26. while(1)
    27. {
    28. printf("please input data:\n");
    29. fgets(data, 100, stdin);
    30. printf("pthread2 send data\n");
    31. flag = 1;
    32. }
    33. }
    34. int main(int argc, char *argv[])
    35. {
    36. pthread_t tid1, tid2;
    37. // 创建线程1,用来接收数据
    38. errno = pthread_create(&tid1, NULL, recv_routine, NULL);
    39. if(errno == 0)
    40. {
    41. printf("pthread create recv_routine success, tid = %ld\n", tid1);
    42. }
    43. else
    44. {
    45. perror("pthread create recv_routine fail\n");
    46. }
    47. // 创建线程2,用来发送数据
    48. errno = pthread_create(&tid2, NULL, send_routine, NULL);
    49. if(errno == 0)
    50. {
    51. printf("pthread create send_routine success, tid = %ld\n", tid2);
    52. }
    53. else
    54. {
    55. perror("pthread create send_routine fail\n");
    56. }
    57. // 一定要加这个,否则主函数直接退出,相当于进程退出,所有线程也退出
    58. // 或者加上while(1)等让主函数不退出
    59. pthread_exit(0);
    60. return 0;
    61. }

    六、总结

            线程可以提供系统的并发性,开销比进程更加小,但是不如进程健壮,移植性好。线程有自己的属性,下一篇博客将讲解。

  • 相关阅读:
    MySQL权限控制、分区表、快速复制表
    Tensorflow亲妈级安装教程(CPU和GPU版)
    【微信小程序】用painter插件生成海报分享朋友圈简单教程
    竞赛选题 疲劳驾驶检测系统 python
    2023年中国一次性医用内窥镜市场发展现状分析:相关产品进入上市高峰期[图]
    PAT 乙级1085 PAT单位排行
    nginx异常重启
    JDK并发修改异常的一个“BUG“
    这是什么代码帮我看看
    d不扫描来缩短垃集
  • 原文地址:https://blog.csdn.net/AABond/article/details/133418863