多线程排序-真香

​来源:公众号【编程珠玑】

作者:守望先生

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++/数据结构与算法/计算机基础/工具等原创技术文章。后台免费获取经典电子书和视频资源

 

你可能感兴趣的:(c-c++)