多线程程序比不使用线程慢

时间:2019-04-29 14:29:08

标签: c multithreading multiprocessing mergesort

我编写了一个用于合并排序的程序,该程序使用多线程和多处理对随机整数进行排序。

这是我的代码:

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>
#include <wait.h>
#include <semaphore.h>
#include <stdbool.h>
#include <getopt.h>

// #include <sys/stat.h>
// #include <sys/syscall.h>
// #include <sys/types.h>

int stNiti = 0;
int *stProcesov = 0;
int prestejNiti = 0;
int prestejProcese = 0;
int stMax = 500; //privzeto stevilo niti/procesov
pthread_t *threadArr;
sem_t *lockThread;
sem_t *lockProc;

struct threadArg{
  pthread_t id;
  int *array;
  int min;
  int max;
  void (*submergeSortHelper)(int*, int, int, int, int);
} ;
//typedef submergeSortArg

// int sestej(int *arr, int len, int skok, int zacetna_vrednost) {
//
// }
bool checkArray(int* array, int size);
void printHelp(int argc, char** argv);
void submergeSortSimple(int* array, int min1, int max1, int min2, int max2);
void submergeSortProc(int* array, int min1, int max1, int min2, int max2);
void submergeSortThread(int* array, int min1, int max1, int min2, int max2);
void mergeSort(int *array, int min, int max, void(*submergeSort)(int *, int, int, int, int));
void merge(int *arr,int min,int mid,int max);

// preprosta implementacija mergeSort rekurzije,
// samo klicemo margeSort za levo in desno polovico
// v istem procesu/isti niti
void submergeSortSimple(int* array, int min1, int max1, int min2, int max2){
    mergeSort(array, min1, max1, submergeSortSimple);
    mergeSort(array, min2, max2, submergeSortSimple);
}

void* thread_fun(void *args) {
  struct threadArg *arg = (struct threadArg*) args;
  mergeSort(arg->array, arg->min, arg->max, arg->submergeSortHelper);
}

// TODO: funkcija ki paralelizira sortiranje z uporabo procesov
// za preprosto paralelizacijo samo izvedemo vsak klic mergeSort
// funkcije v svojem procesu, in počakamo, da se klica zaključita
void submergeSortProc(int* array, int min1, int max1, int min2, int max2){
    int index = 0;

    if(*stProcesov <= stMax-1) {
      sem_wait(lockProc);
      *stProcesov+=1;
      sem_post(lockProc);
      // printf("Proces %d \n", *stProcesov);
      int proces = fork();

        if(proces > 0){//stars
            wait(0);
            wait(0);
            mergeSort(array,min2,max2,submergeSortProc);
        }
        else if(proces == -1){
            perror("Napaka pri fork()!\n");
            _exit(-1);
        }
        else{
            mergeSort(array,min1,max1,submergeSortProc);
            _exit(0);
        }

    }
    else { //dosezen je maksimum stevila procesov
      mergeSort(array, min1, max1, submergeSortSimple);
      // sleep(1);
      mergeSort(array, min2, max2, submergeSortSimple);
    }
    // stProcesov = index;
    // return;

    if(*stProcesov < stMax) {
      prestejProcese = *stProcesov+1;
    }
    else {
      prestejProcese = *stProcesov;
    }

}

// TODO: funkcija, ki paralelizira sortiranje z uporabo niti
// za preprosto paralelizacijo samo izvedemo vsak klic mergeSort
// funkcije v svoji niti, in počakamo, da se klica zaključita
// void *helperThreadFunction(void *args);

void submergeSortThread(int* array, int min1, int max1, int min2, int max2){
    int index = 0;

    if(stNiti <= stMax-1) {
      //sinhroinizacija - narediti semafor
      sem_wait(lockThread);
      index = stNiti;
      stNiti++;
      // printf("St niti: %d\n", stNiti);
      sem_post(lockThread);

      int statusC = 0, statusJ = 0; //statusa za join in create
      struct threadArg prvi;
      struct threadArg drugi;

      prvi.array = array;
      drugi.array = array;
      prvi.max = max1;
      drugi.max = max2;
      prvi.min = min1;
      drugi.min = min2;
      prvi.submergeSortHelper = submergeSortThread;
      drugi.submergeSortHelper = submergeSortThread;

      //ustvarimo novo nit
      statusC = pthread_create(&threadArr[index], 0, thread_fun, (void*)&prvi);

      if(statusC != 0) {
        perror("Napaka pri pthread_create");
        _exit(-1);
      }

      statusJ = pthread_join(threadArr[index], 0);

      if(statusJ != 0) {
        perror("Napaka pri pthread_join");
        _exit(-1);
      }

      thread_fun((void*)&drugi);
      // pthread_t tid1, tid2;

      //ustvarimo novi niti za mergeSort
      // pthread_create(&tid1, 0, &thread_fun, &prvi);
      // pthread_create(&tid2, 0, &thread_fun, &drugi);


      // pthread_join(tid1, 0);
      // pthread_join(tid2, 0);
    }
    else {
      //maxNiti je dosezeno, preostanek uredimo z navadnim sortiranjem
      struct threadArg prvi;
      struct threadArg drugi;

      prvi.array = array;
      drugi.array = array;
      prvi.max = max1;
      drugi.max = max2;
      prvi.min = min1;
      drugi.min = min2;
      prvi.submergeSortHelper = submergeSortThread;
      drugi.submergeSortHelper = submergeSortThread;

      thread_fun((void*)&prvi);
      thread_fun((void*)&drugi);
    }
    // prestejNiti = stNiti; //stevec za niti
    if(stNiti < stMax) {
      prestejNiti = stNiti+1;
    }
    else {
      prestejNiti = stNiti;
    }
}

// mergeSort in merge funkciji
// ti dve izvajata dejansko sortiranje

// void mergeSort(int *array, int min, int max, void(*submergeSort)(int *, int, int, int, int) )
//
// int *array
//   kazalec na tabelo števil, ki jih urejamo
//
// int min, int max
//   indeks prvega in zadnjega števila v tabeli,
//   označujeta interval, ki ga sortiramo
//
// void (*submergeSort)(int *array, int min1, int max1, int min2, int max2)
//   kazalec na funkcijo, ki naj kliče mergeSort za dva podintervala
//   in vrne, ko sta oba intervala sortirana
void mergeSort(int *array, int min, int max, void(*submergeSort)(int *, int, int, int, int) ){
    int mid;
    if(min < max){
        mid=(min+max)/2;

        submergeSort(array, min, mid, mid+1, max);

        merge(array, min, mid, max);
    }
}

// void merge(int *arr, int min,int mid,int max)
//
// int *arr
//   kazalec na tabelo
//
// int min, int mid, int max
//   indeksi na del tabele, ki jih je potrebno združiti
//   min je začetni indeks prve podtabele, mid je zadnji indeks
//   prve podtabele in max je zadnji indeks druge podtabele
//
// metoda zdruzi dve sosednji sortirani podtabeli,
// tako da je nova podtabela tudi sortirana
void merge(int *arr, int min,int mid,int max)
{
    // drugi korak algoritma mergeSort
    int *tmp = malloc((max-min+1)*sizeof(int));
    int i,j,k,m;
    j=min;
    m=mid+1;
    for(i=min; j<=mid && m<=max ; i++)
    {
        if(arr[j]<=arr[m])
        {
            tmp[i-min]=arr[j];
            j++;
        }
        else
        {
            tmp[i-min]=arr[m];
            m++;
        }
    }
    if(j>mid)
    {
        for(k=m; k<=max; k++)
        {
            tmp[i-min]=arr[k];
            i++;
        }
    }
    else
    {
        for(k=j; k<=mid; k++)
        {
            tmp[i-min]=arr[k];
            i++;
        }
    }
    for(k=min; k<=max; k++)
        arr[k]=tmp[k-min];

    free(tmp);
}


int main(int argc, char *argv[])
{
    int i;
    int size;
    int *arr;
    pthread_t threads[stMax];
    int **temp;
    #define NO_PAR 0
    #define PROC_PAR 1
    #define THREAD_PAR 2
    int technique= NO_PAR;
    int number;
    char *value = NULL;

    void (*submergeSortFun)(int *, int, int, int, int);
    submergeSortFun = submergeSortSimple;
    while(1){
        int c;
        c = getopt(argc, argv, "ptn");
        if(c==-1){
          // printf("!!!Negativno!!!");
            break;
        }
        switch(c){
            case 'p':
                technique = PROC_PAR;
                submergeSortFun = submergeSortProc;
                 stMax = atoi(argv[3]);
                break;
            case 't':
                technique = THREAD_PAR;
                submergeSortFun = submergeSortThread;
                stMax = atoi(argv[3]);
                printf("\nStMax: %d\n", stMax);
                break;
            case 'n':
                size = atoi(argv[4]);
                printf("\nSize: %d\n\n\n", size);
                break;
            default:
                printHelp(argc, argv);
                return 0;
        }
    }
    for(int i=0; i<argc; i+=1) {
      printf("argv[%d]: %s\n", i, argv[i]);
    }


    if(optind >= argc){
        printHelp(argc, argv);
        return -1;
    }

    //size = atoi(argv[optind]);
    // size = atoi(argv[4]);

    // TODO: inicializacija za razlicne tehnike
    switch(technique){
        case NO_PAR:
            arr = malloc(sizeof(int)*size);
            break;
        case PROC_PAR:
            // inicializacija za uporabo procesov
            // ustvariti je potrebno deljen pomnilnik, semafor, ...
            sem_unlink("lockProc");
            lockProc = sem_open("lockProc", O_RDWR|O_CREAT|O_EXCL, 0666, 1);
            stProcesov = mmap (0,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS, 0, 0);
            arr=mmap (0,sizeof(int)*size,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS, 0, 0);
            *stProcesov = 0;
            break;
        case THREAD_PAR:
            // inicializacija za uporabo niti
            // tukaj potrebujete morebitne sinhronizacijske strukture
            arr = malloc(sizeof(int)*size);
            sem_unlink("lockThread");
            lockThread=sem_open("lockThread", O_RDWR|O_CREAT|O_EXCL, 0660, 1);
            threadArr = threads;
            break;
    }

    char buffer[101];
    for(i=0; i < size; i+=1){
        // preberi binarne vrednosti
        read(0, &arr[i], 4);
    }
    int stevilo = 10000;
    for(int i=0; i<1000; i+=1) {
      arr[i] = stevilo;
      stevilo-=2;
    }


    // printf("Pred urejanjem: ");
    // for(i=0; i<size; i++){
    //     printf("%d ",arr[i]);
    // }
    // printf("\n");

    mergeSort(arr, 0, size-1, submergeSortFun);

    // printf("Po urejanju: ");
    // for(i=0; i<size; i++){
    //     printf("%d ",arr[i]);
    // }
    printf("\n\n\n");
    if(prestejNiti > 0 && prestejProcese == 0) {
      printf("St. niti, ki so sodelovale: %d \n\n", prestejNiti);
    }
    else if(prestejProcese > 0 && prestejNiti == 0) {
      printf("St. procesov, ki so sodelovali: %d\n\n", prestejProcese);
    }

    // TODO: ciscenje za razlicnimi tehnikami
    switch(technique){
        case NO_PAR:
            free(arr);
            break;
        case PROC_PAR:
            // *temp = stProcesov;
            munmap(arr, sizeof(int)*size);
            munmap(stProcesov, sizeof(int));
            sem_close(lockProc);
            sem_unlink("lockProc");
            break;
        case THREAD_PAR:
            sem_close(lockThread);
            sem_unlink("lockThread");
            break;
    }

    return 0;
}

void printHelp(int argc, char** argv){
    printf("uporaba\n");
    printf("%s <opcija> <n>\n",argv[0]);
    printf("\tn je število celih števil prebranih z standardnega vhoda\n");
    printf("\tfunkcije prebere n*4 bajtov v tabelo in jih sortira\n");
    printf("opcije:\n");
    printf("-p\n");
    printf("\tparalelizacija s pomočjo procesov\n");
    printf("-t\n");
    printf("\tparalelizacija s pomočjo niti\n");
}

现在,当我测量时间时,可以看到当我使用50或100个线程或进程时,它比仅使用一个线程花费更多的时间。 我使用Xubuntu,并使用终端进行测量(时间cat / dev / urandom | ./mergeSort -p -n 200 1000000000)

有人可以告诉我这是否正常,还是我在代码中的某个地方犯了错误。

谢谢。

2 个答案:

答案 0 :(得分:5)

从根本上讲,需要完成的工作量始终是相同的。 将其视为大量的“待办事项”清单。即使您在多个线程甚至是多台计算机之间划分,工作列表上的所有项目仍需要完成才能使排序成功。

现在,考虑单核CPU的最简单情况。

即使它能够运行多个线程,该单核CPU现在仍需要完成必填任务列表中的所有内容,并且 还花时间安排线程并在它们之间进行切换。这肯定比仅一个线程全速运行的速度要慢。

如果您有多个核心,则可以在这些核心之间划分工作,每个核心都可以解决部分问题。划分工作并重新组合结果仍然存在一些开销。因此8核处理器的速度 不是 是8倍。它将比单核快6到7倍。

这仅适用于尽可能多的内核。如果现在尝试在8个内核之间调度100个线程,则每个内核大约管理12个线程!每个内核都花时间安排时间并在线程之间交换,并使高速缓存数据与其他内核保持一致。 您的CPU将负担繁重的工作,同时尝试获取原始的实际排序数据的强制性待办事项列表。

换句话说,您可以调度与内核一样多的线程,但不能调度更多。之后,您只需要为内核做更多的多线程工作即可,而无需再为解决实际问题增加强大的功能。

答案 1 :(得分:2)

因此,您想使用线程来加速mergesort。

基本思想:将数组切成与核心相同数量的块,并行合并。然后合并大块。由于您有两个以上的线程,因此也可以部分并行执行。

请不要产生比内核更多的线程来加速CPU。这适得其反。