“多线程”简介及其C代码实现框架
在一些计算机专业相关的书籍中,大家经常听说“多线程”这个概念。那么什么是“多线程”?什么时候使用“多线程”?在程序设计中使用“多线程”有 什么好处呢?很多刚入职的程序员也对“多线程”感到非常的好奇,认为它很“高大上”。本文对“多线程”进行了简单的介绍,并给出了其C代码的实现框架。
要想理解“多线程”,那么就要先从“单线程”说起。
大家都知道工厂“流水线”作业,里面的工序是一环扣一环的,只有前面的一道工序完成之后,才能够启动下一道工序。这其实和“单线程”的原理非常的相似。
在“单线程”里面,程序的功能是顺序执行的,只有前面的流程都成功执行,后面的流程才能够被执行到。例如,要实现一个话单文件生成、上传和删除的程序,使用“单线程”程序来完成,那么其流程如图1所示。
图1 “单线程”程序
大家也许注意到了,图1中的生成文件、上传文件和删除文件的流程其实可以独立开来。也就是说,这三个流程是互不影响的。这样也就诞生了“多线程”的概念。
“多线程”,顾名思义,就是多个“单线程”,每个线程独立地完成相关的功能。如图1所示的程序,如果用“多线程”来实现,那么其流程如图2所示。
图2 “多线程”程序从图2可以看出,当程序启动之后,线程1、线程2和线程3是同时运行的。线程1仅用于生成话单文件,线程2仅用于上传话单文件,线程3仅用 于删除过期的话单文件。这样一来,任何一个线程执行成功与否对另外两个线程都没有影响,真正地实现了程序的“并行”。
“多线程”在大型软件程序中有着很广泛的应用,其优点如下:
第一,将原来在一个大流程中实现的功能放到了多个小流程中,程序更加的简洁和易于阅读。
第二,将不同的功能放到不同的线程中,提高了程序的执行效率。
第三,“多线程”使得程序的模块化更强,有利于追踪程序执行过程和排查问题。
“多线程”的C代码框架
/********************************************************************** * 版权所有 (C)2015, Zhou Zhaoxiong。 * * 文件名称:ThreadCreate.c * 文件标识:无 * 内容摘要:演示多线程的创建 * 其它说明:无 * 当前版本:V1.0 * 作 者:Zhou Zhaoxiong * 完成日期:20151029 * **********************************************************************/ #include <stdio.h> #include <stdlib.h> #include <pthread.h> // 重定义数据类型 typedef signed int INT32; typedef unsigned int UINT32; // 宏定义 #define THREAD_NUM 5 // 线程个数 // 函数声明 void ScanTask(void *pParam); void ProcessTask(void *pParam); /********************************************************************** * 功能描述:主函数 * 输入参数:无 * 输出参数:无 * 返 回 值:无 * 其它说明:无 * 修改日期 版本号 修改人 修改内容 * ------------------------------------------------------------------- * 20151029 V1.0 Zhou Zhaoxiong 创建 ***********************************************************************/ INT32 main() { pthread_t MultiHandle = 0; // 多线程句柄 pthread_t SingleHandle = 0; // 单线程句柄 UINT32 iLoopFlag = 0; INT32 iRetVal = 0; // 创建线程函数的返回值 // 循环创建线程 for (iLoopFlag = 0; iLoopFlag < THREAD_NUM; iLoopFlag ++) { iRetVal = pthread_create(&MultiHandle, NULL, (void * (*)(void *))(&ScanTask), (void *)iLoopFlag); if (0 != iRetVal) { printf("Create ScanTask %d failed!\n", iLoopFlag); return -1; } } // 单独创建线程 iRetVal = pthread_create(&SingleHandle, NULL, (void * (*)(void *))(&ProcessTask), NULL); if (0 != iRetVal) { printf("Create ProcessTask failed!\n"); return -1; } return 0; } /********************************************************************** * 功能描述: 扫描线程 * 输入参数: pParam-线程编号 * 输出参数: 无 * 返 回 值: 无 * 其它说明: 无 * 修改日期 版本号 修改人 修改内容 * ---------------------------------------------------------------------- * 20151029 V1.0 Zhou Zhaoxiong 创建 ************************************************************************/ void ScanTask(void *pParam) { UINT32 iThreadNo = 0; // 线程编号 iThreadNo = (UINT32)pParam; // 获取线程编号 printf("Now, into ScanTask[%d].\n", iThreadNo); // 打印包含线程编号的消息 // 进行后续操作 } /********************************************************************** * 功能描述: 处理线程 * 输入参数: 无 * 输出参数: 无 * 返 回 值: 无 * 其它说明: 无 * 修改日期 版本号 修改人 修改内容 * ---------------------------------------------------------------------- * 20151029 V1.0 Zhou Zhaoxiong 创建 ************************************************************************/ void ProcessTask(void *pParam) { printf("Now, into ProcessTask.\n"); // 进行后续操作 }
说明:
第一,本程序利用pthread_create函数来创建线程,该函数的原型是:
int pthread_create(pthread_t tidp,const pthread_attr_t *attr,(void )( start_rtn)(void ),void *arg);
第一个参数为指向线程标识符的指针,在本程序中为MultiHandle和SingleHandle。
第二个参数用来设置线程属性。
第三个参数是线程运行函数的起始地址,在本程序中即为函数名。
第四个参数是运行函数的参数,当同时创建多个功能相同的线程时,该参数表示线程编号。
第二,在Linux下,该程序的编译命令为:gcc -g -o ThreadCreate ThreadCreate.c –lpthread。注意,最后的“–lpthread”是不能省略的,否则程序编译不通过。因为pthread并非Linux系统的默认库,而要在 Linux中将其作为一个库来使用,就需要加上“-lpthread”或“-pthread”以显式链接该库。第三,在程序的多线程中,建议不要同时对同 一个全局变量进行加、减等操作。如果确实需要这样做,要注意在关键代码处使用加锁操作。
随着软件功能的增强,随之而来的就是程序复杂度的提升,这也使得程序从“单线程”到“多线程”的转变成为必然。
“多线程”和“单线程”分别对应“并行”和“串行”,是软件开发人员必须要掌握的一种程序设计的方法。设计合理的“多线程”程序不仅逻辑清晰、易于阅读,而且程序的执行效率高,对于软件产品效率和质量的提升具有很重要的意义。
最后,推荐大家阅读一篇文章《进程与线程的一个简单解释》( http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html ),本文以图形的方式展示了进程与线程的区别,及有关操作系统的其它概念,值得一读。
原文链接: http://www.daxixiong.com/?/article/12
本文中的代码已提交到GitHub上,请见: https://github.com/zhouzxi/ThreadCreate