单线程和多线程实现归并排序并对比测试

一、编程目的

  1. 熟悉多线程编程的基本模式;
  2. 对比多线程和单线程编程的优缺点。

二、代码实现

代码主要涉及到三个文件:生成数组代码:gen_arr.c、单线程归并merge_sort.c、多线程归并merge_sort_thread.c

1.随机生成数组

gen_arr.c

/*
生成200000大小数组,并写入文件arr.txt中
编译命令:
gcc gen_arr.c -o gen_arr
./gen_arr
*/
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
int arr_num = 200000;
char fileName[] = "arr.txt";
int main(void)
{
    FILE *fp = fopen(fileName, "w");
    if (fp == NULL)
    {
        printf("Error in opening file %s.\n", fileName);
        exit(-1);
    }
    fprintf(fp, "%d%c", arr_num, '\n');
    int num;
    srand((unsigned int)time(0)); //修改种子
    for (int i = 0; i < arr_num; i++)
    {
        num = rand();
        fprintf(fp, "%d%c", num, ' ');
    }
    fclose(fp);

    fp = fopen(fileName, "r");
    if (fp == NULL)
    {
        printf("Error in opening file %s.\n", fileName);
        exit(-1);
    }

    fscanf(fp, "%d", &arr_num);
    printf("arr_num = %d\n", arr_num);
    int *a = (int *)malloc(arr_num * sizeof(int));
    for (int i = 0; i < arr_num; i++)
    {
        fscanf(fp, "%d", &a[i]);
    }
    for (int i = 0; i < arr_num; i++)
    {
        printf("%d ", a[i]);
    }
    printf("\n");
    return 0;
}

2.单线程归并排序

merge_sort.c

/*
单线程归并排序
编译命令:(注意要先使用gen_arr程序生成数组)
gcc merge_sort.c -o merge_sort
time ./merge_sort
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ARR_LEN 200000
int arr_num;
char fileName[] = "arr.txt";
int arr[ARR_LEN] = {0};

void Display(int *arr, int n)
{
    for (register int i = 0; i < n; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

void merge(int *arr, int L, int M, int R)
{
    int LEFT_SIZE = M - L;
    int RIGHT_SIZE = R - M + 1;
    int left[LEFT_SIZE];
    int right[RIGHT_SIZE];
    int i, j, k;

    //将原数组左边分到一个新数组left中
    for (i = L; i < M; i++)
    {
        left[i - L] = arr[i];
    }
    //将原数组右边分到一个新数组right中
    for (i = M; i <= R; i++)
    {
        right[i - M] = arr[i];
    }
    i = 0;
    j = 0;
    k = L;
    while (i < LEFT_SIZE && j < RIGHT_SIZE)
    {
        //如果left数组的第一个数字比right数组第一个数字小就把它放到新数组里
        if (left[i] < right[j])
        {
            arr[k] = left[i];
            i++;
            k++;
        }
        else
        {
            arr[k] = right[j];
            j++;
            k++;
        }
    }
    while (i < LEFT_SIZE)
    {
        arr[k] = left[i];
        i++;
        k++;
    }
    while (j < RIGHT_SIZE)
    {
        arr[k] = right[j];
        j++;
        k++;
    }
}

void mergesort(int *arr, int L, int R)
{
    int M = (L + R) / 2;
    if (L == R)
    {
        return;
    }
    else
    {
        // printf("归并排序中:");
        // Display(arr, 20);
        mergesort(arr, L, M);
        mergesort(arr, M + 1, R);
        merge(arr, L, M + 1, R);
    }
}

void read_data()//从文件中读取数组数据
{
    FILE *fp = fopen(fileName, "r");
    if (fp == NULL)
    {
        printf("Error in opening file %s.\n", fileName);
        exit(-1);
    }

    fscanf(fp, "%d", &arr_num);
    for (int i = 0; i < arr_num; i++)
    {
        fscanf(fp, "%d", &arr[i]);
    }
}
int main()
{
    read_data();

    // printf("原数组为:\n");
    // Display(arr, arr_num);
    // printf("\n");
    int L = 0;
    int R = arr_num - 1;
    mergesort(arr, L, R);
    // printf("\n归并排序后:\n");
    // Display(arr, R + 1);
    return 0;
}

3.多线程归并排序

这里规定了在排序过程中使用的最大线程数量,因为在不断调试程序的过程中,发现数组大小、线程数量都对多线程排序的效率有影响。

  • 数组规模太小的话,使用多线程的代价大,内核处理时间占很多;数组规模太大的话,递归层数多,可能开启的线程也多,同样地系统内核处理时间就会变得很长。
  • 线程开启数量太少,并发程度不高,和普通的单线程没有太大区别;线程开启数量太多,花销又很大,内核处理时间也太长,还有线程数量过多导致程序段错误的风险(经过试验得出)。‘

所以我只能在确定数据规模和内容(200000个数据)的情况下,不断地试验最多开启多少个线程合适,最后发现选取线程数量在50~100之间效率比较高。

/*
多线程归并排序
编译命令:(注意要先使用gen_arr程序生成数组)
gcc merge_sort_thread.c -o merge_sort_thread -lpthread
time ./merge_sort_thread
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>
#define ARR_LEN 200000
#define MAX_THREAD_SIZE 75 //规定最多开启线程的数量
int arr_num;
char fileName[] = "arr.txt";
int thread_num = 0;           //开启线程的数量,初始化为0
pthread_mutex_t Mutex;        //修改thread_num时用到的锁(互斥信号量)
int arr[ARR_LEN] = {0};       //数组
void Display(int *arr, int n) //打印数组用
{
    for (register int i = 0; i < n; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

void merge(int L, int M, int R) //合并部分,没有用到多线程
{
    int LEFT_SIZE = M - L;
    int RIGHT_SIZE = R - M + 1;
    int left[LEFT_SIZE];
    int right[RIGHT_SIZE];
    int i, j, k;

    //将原数组左边分到一个新数组left中
    for (i = L; i < M; i++)
    {
        left[i - L] = arr[i];
    }
    //将原数组右边分到一个新数组right中
    for (i = M; i <= R; i++)
    {
        right[i - M] = arr[i];
    }
    i = 0;
    j = 0;
    k = L;
    while (i < LEFT_SIZE && j < RIGHT_SIZE)
    {
        //如果left数组的第一个数字比right数组第一个数字小就把它放到新数组里
        if (left[i] < right[j])
        {
            arr[k] = left[i];
            i++;
            k++;
        }
        else
        {
            arr[k] = right[j];
            j++;
            k++;
        }
    }
    while (i < LEFT_SIZE)
    {
        arr[k] = left[i];
        i++;
        k++;
    }
    while (j < RIGHT_SIZE)
    {
        arr[k] = right[j];
        j++;
        k++;
    }
}

void *mergesort(void *arg) //排序主体部分,用到了多线程
{
    int *m_arg = (int *)arg;
    int L = m_arg[0];
    int R = m_arg[1];
    // printf("L=%d,R=%d\n", L, R);
    int M = (L + R) >> 1;
    if (L >= R)
    {
        return NULL;
    }
    int arg1[2], arg2[2];
    arg1[0] = L;
    arg1[1] = M;

    arg2[0] = M + 1;
    arg2[1] = R;
    int start_thread = 0; //决定是否开启线程
    pthread_t p1, p2;
    pthread_mutex_lock(&Mutex);
    if (thread_num + 2 <= MAX_THREAD_SIZE) //其实与最大线程数量存在一定偏差(不超过2)
    {                                      //如果没有超过最大线程数量,那么就开启两个子线程
        pthread_create(&p1, NULL, mergesort, arg1);
        pthread_create(&p2, NULL, mergesort, arg2);
        thread_num += 2;
        start_thread = 1;
    }
    pthread_mutex_unlock(&Mutex);
    // mergesort(arg1);
    if (start_thread == 1)
    {
        pthread_join(p1, NULL);
        pthread_join(p2, NULL);
        pthread_exit(NULL);
    }
    else //超过最大线程数量,用普通的归并排序
    {
        mergesort(arg1);
        mergesort(arg2);
    }
    merge(L, M + 1, R);
}
void read_data()
{
    FILE *fp = fopen(fileName, "r");
    if (fp == NULL)
    {
        printf("Error in opening file %s.\n", fileName);
        exit(-1);
    }

    fscanf(fp, "%d", &arr_num);
    for (int i = 0; i < arr_num; i++)
    {
        fscanf(fp, "%d", &arr[i]);
    }
}
int main()
{
    read_data();
    int arg[2];
    arg[0] = 0;
    arg[1] = ARR_LEN;
    pthread_t tid;
    pthread_create(&tid, NULL, mergesort, arg);
    pthread_join(tid, NULL);
    printf("使用到的线程数量为:%d。\n", thread_num);
    return 0;
}

三、对比测试

先使用命令:

gcc gen_arr.c -o gen_arr
./gen_arr

生成大小为200000的数组。

然后用命令:

gcc merge_sort.c -o merge_sort
time ./merge_sort

gcc merge_sort_thread.c -o merge_sort_thread -lpthread
time ./merge_sort_thread

分别测试单、多线程归并排序的时间,统计10次运行的平均值:

单线程最大线程数50最大线程数75(实际是74)最大线程数100
10次平均运行时间 0.0537 s 0.0537s 0.0537s 0.0451 0.0451 0.0451 0.0522 s 0.0522s 0.0522s 0.0454 0.0454 0.0454

可以看到在一般情况下多线程归并排序确实比单线程要快一点,数据量越大,多线程的优点应该愈发明显。但一味地开启子线程反而会降低程序的运行效率,甚至可能会导致程序崩溃!因此限制最大线程数是有必要的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值