多线程排序-真香!
- 2019 年 12 月 27 日
- 笔记
来源:公众号【编程珠玑】
作者:守望先生
ID:shouwangxiansheng 在《系统编程-多线程》中已经了解了多线程的一些特点,其中包括快!那么今天就来看看如何利用多线程来排序。
思路
我们的思路是这样的:
- 假设有N个线程,则将数组数M据分为N组
- 每个线程对其中的一组数据使用库函数提供的快速排序算法
- 所有线程排序完成后,将每组排序好的数组合并
举个例子,使用4个线程对11个数据进行排序:
12,10,4,7,9,6,8,1,5,16,11
由于4不能被10整除,因此,前面三个线程,每个负责排序10%(4-1)= 3三个数,最后一个线程负责排序最后两个数。
假设这4个线程都完成了自己的工作后,内容如下:
比较每组的第一个,选出最小的一个,这里是线程2的1,放到新数组的下标0处最后由主线程将已经排好的每组进行合并:
- 将1放到新的数组最开始的位置,线程的下次计较的内容后移,即下次比较时,比较线程2的第二个数。
- 循环比较
最终可以得到合并的数据:
1 4 5 6 7 8 9 10 11 12 16
屏障
通过上面的分析,我们需要多个线程进行排序后,一起交给主线程合并,因此需要有方法等待所有线程完成事情之后,再退出。 在《系统编程-多线程》中介绍了pthread_join,今天我们使用pthread_barrier_wait。
#include <pthread.h> int pthread_barrier_destroy(pthread_barrier_t *barrier); int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned count); int pthread_barrier_wait(pthread_barrier_t *barrier);
在解释之前说明一下基本原理,pthread_barrier_wait等待某一个条件达到(计数到达),一旦达到后就会继续往后执行。当然了,如果你希望各个线程完成它自己的工作,主线程再进行合并动作,则你等待的数量可以再加一个。:
//来源:公众号【编程珠玑】 //barrier.c #include <stdio.h> #include <pthread.h> #include <unistd.h> pthread_barrier_t b; void *workThread(void * arg) { printf("thread %dn",*(int*)arg); pthread_barrier_wait(&b); return (void*)0; } int main(void) { int threadNum = 4; int err; /*计数为创建线程数+1*/ pthread_barrier_init(&b,NULL,threadNum + 1); int i = 0; pthread_t tid; /*创建多个线程*/ for(i = 0;i < threadNum; i++) { err = pthread_create(&tid,NULL,workThread,(void*)&i); if(0 != err) { printf("create thread failedn"); return -1; } printf("tid:%ldn",tid); usleep(10000); } pthread_barrier_wait(&b); printf("all thread finishedn"); /*销毁*/ pthread_barrier_destroy(&b); return 0; }
其中,pthread_barrier_init用来初始化相关资源,而pthread_barrier_destroy用来销毁相关资源。 编译运行:
$ gcc -o barrier barrier.c -lpthread $ ./barrier tid:140323085256448 thread 0 tid:140323076863744 thread 1 tid:140323068471040 thread 2 tid:140323060078336 thread 3 all thread finished
比较函数
为了使用qsort函数,我们需要实现自己的比较函数,参考《高级指针话题-函数指针》和《快速排序你真的会了吗?》:
//来源:公众号【编程珠玑】 //https:www.yanbinghu.com /*比较函数*/ int compare(const void* num1, const void* num2) { long l1 = *(long*)num1; long l2 = *(long*)num2; if(l1 == l2) return 0; else if(l1 < l2) return -1; else return 1; }
合并
对于每个线程完成它自己的任务之后,需要合并所有内容,关于合并的逻辑前面已经举例了,这里不再多介绍。
//来源:公众号【编程珠玑】 //https://www.yanbinghu.com /*要排序的数组信息*/ typedef struct SortInfo_t { long startIdx; //数组启始下标 long num;//要排序的数量 }SortInfo; /*合并线程已经排序好的内容*/ void merge(SortInfo *sortInfos,size_t threadNum) { long idx[threadNum]; memset(idx,0,threadNum); long i,minidx,sidx,num; for(i = 0;i < threadNum;i++) { idx[i] = sortInfos[i].startIdx; } for(sidx = 0;sidx < NUM;sidx++) { num = LONG_MAX; for(i = 0;i < threadNum;i++) { if(idx[i] < (sortInfos[i].startIdx + sortInfos[i].num) && (nums[idx[i]] < num)) { num = nums[idx[i]]; minidx = i; } } snums[sidx] = nums[idx[minidx]]; idx[minidx]++; } }
随机数生成
关于生成方法,参考《随机数生成的N种姿势》。
完整代码
由于完整代码比较长,这里就不贴出了,有兴趣的可以访问: https://paste.ubuntu.com/p/yT2cm8G55H/ 或阅读原文查看。
运行结果
对800W数据进行排序,排序时间:
$ threadSort 1 thread num:1 time 2.369488
使用4个线程时:
$ threadSort 4 thread num:4 time 1.029097
可以看到速度提升是比较明显的。
总结
可以看到使用4线程排序要比单个线程排序快很多,不过以上实现仅供参考,本文例子可能也存在不妥之处,请根据实际数据情况选择合适的排序算法。但是,多线程就一定快吗?敬请关注下一篇。
参考:《unix环境高级编程》