来源:公众号【编程珠玑】
作者:守望先生
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
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
#include
#include
pthread_barrier_t b;
void *workThread(void * arg)
{
printf("thread %d\n",*(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 failed\n");
return -1;
}
printf("tid:%ld\n",tid);
usleep(10000);
}
pthread_barrier_wait(&b);
printf("all thread finished\n");
/*销毁*/
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://www.yanbinghu.com/2019/12/23/52416.html
对800W数据进行排序,排序时间:
$ threadSort 1
thread num:1
time 2.369488
使用4个线程时:
$ threadSort 4
thread num:4
time 1.029097
可以看到速度提升是比较明显的。
可以看到使用4线程排序要比单个线程排序快很多,不过以上实现仅供参考,本文例子可能也存在不妥之处,请根据实际数据情况选择合适的排序算法。但是,多线程就一定快吗?敬请关注下一篇。
参考:《unix环境高级编程》
我有一个问题,用了多线程后,两个问题有了现在
面试必问:进程和线程有什么区别?
快速排序你真的会了吗?
关注公众号【编程珠玑】,获取更多Linux/C/C++/数据结构与算法/计算机基础/工具等原创技术文章。后台免费获取经典电子书和视频资源