基於POSIX實現一個文件下載線程池

先理清思路:
線程池需要維護一個任務隊列,允許配置活動的線程數,線程池從任務隊列中取任務,根據拿到的任務執行響應處理,所以每個任務是這樣一個結構體:

typedef void* (*FUNC_POINT)(void* arg);	//定義一個函數指針

typedef struct Task //任務結構體,
{
  //void* (*Handler)(void* arg);  		//處理任務的函數
  FUNC_POINT Handler;
  void* url;                            		//參數
  struct Task* next;                    //先一個Task的指針
}Task;

線程池Pool也是一個結構體,該結構體如下:

typedef struct Pool
{
  Task* head;            //任務隊列頭指針,,指向任務隊列的第一個任務
  Task* tail;            //任務隊列尾指針,,指向任務隊列的最後一個任務
  int maxThreads;        //線程池的最大線程個數
  int workThreads;       //線程池當前的線程數
  int freeThreads;       //線程池空閒的線程數(指的是:已經創建,並且已經把自己任務完成了的線程)

  pthread_mutex_t mut;
  pthread_cond_t cond;
  
  int destroy;          //線程池銷燬標誌(值爲1銷燬)
}Pool;

所以邏輯是這樣的:
我們創建一個線程池,然後往線程池裏加任務。然後創建線程,線程從線程池結構體裏取任務(同時只能由一個線程取任務),取到任務之後調用task結構體中的函數就可。

threadpool_add(Pool* pool, FUNC_POINT fun, void *url);//添加任務到線程池
第一個參數是線程池結構體指針,
第二個參數任務處理函數,就是線程拿到任務之後執行什麼任務;
第三個參數是任務處理函數的參數的地址(因爲我們要傳什麼參數需要視情況而定,可能是int,char*或者其他,所以定義一個void*指針,把參數地址傳過去就行了)

具體代碼如下:
threadpool.h

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <wait.h>
#include <time.h>
#include <errno.h>
#include <sys/time.h>


typedef void* (*FUNC_POINT)(void* arg);	//定義一個函數指針

typedef struct Task //任務結構體,
{
  //void* (*Handler)(void* arg);  		//處理任務的函數
  FUNC_POINT Handler;
  void* url;                            //參數
  struct Task* next;                    //先一個Task的指針
}Task;

typedef struct Pool
{
  Task* head;            //任務隊列頭指針,,指向任務隊列的第一個任務
  Task* tail;            //任務隊列尾指針,,指向任務隊列的最後一個任務
  int maxThreads;        //線程池的最大線程個數
  int workThreads;       //線程池當前的線程數
  int freeThreads;       //線程池空閒的線程數(指的是:已經創建,並且已經把自己任務完成了的線程)

  pthread_mutex_t mut;
  pthread_cond_t cond;
  
  int destroy;          //線程池銷燬標誌(值爲1銷燬)

}Pool;

void* thread_Handler(void* arg);        //線程處理函數
void thread_init(Pool* pool, int max);  //線程初始化


void* thread_Handler(void* arg)
{

  int timeout;
  timeout = 0;

  Pool* pool = (Pool*)arg;
  while(1)
  {
    //上鎖
    pthread_mutex_lock(&pool->mut);
    //增加當前的空閒線程數
    pool->freeThreads++;
    //如果現在沒有任務,並且也沒有受到線程池銷燬的通知
    while(pool->head == NULL && pool->destroy == 0)
    {
      printf("任務隊列空!thread:0x%0x is waiting\n",pthread_self());
   //   int err;
   //   err = pthread_cond_wait(&pool->cond, &pool->mut);
   //   if(err != 0){
   //     printf("pthread_cond_wait is fail\n");
   //     break;
   //   }
   //   break;
      //此處使用pthread_cond_timewait。指定等待指定的時間
      //必須要等,雖然此時任務隊列爲空,但可能有的任務還沒到任務隊列,還在路上
      struct timespec tsp; 
      struct timeval now;
      gettimeofday(&now, NULL);//獲取當前時間
      tsp.tv_sec = now.tv_sec;
      tsp.tv_nsec = now.tv_usec * 1000;
      tsp.tv_sec += 10; //指定等待時間爲10s
      
      int status;
      status = pthread_cond_timedwait(&pool->cond, &pool->mut, &tsp);
      if(status == ETIMEDOUT){
        timeout = 1;
        printf("%d freeThreads\n", pool->freeThreads);
        break;
      }
      
    }
    
    //如果有任務
    if(pool->head != NULL){
      pool->freeThreads--;

      printf("拿到一個任務\n");

      Task* tmp = pool->head;
      pool->head = tmp->next;
      //開始執行任務函數
      //此處要解鎖,執行任務需要一定時間,這段時間內要允許添加任務
      //其他消費者線程能夠進入等待任務
      pthread_mutex_unlock(&pool->mut);
      tmp->Handler(tmp->url);
      free(tmp);

      pthread_mutex_lock(&pool->mut);
    }
    
    //如果現在沒有任務,但是收到線程池銷燬的通知
    if(pool->head == NULL && pool->destroy == 1){
      printf("銷燬通知\n");
      printf("%d workThreads\n",pool->workThreads);
      pool->workThreads--;
      if(pool->workThreads == 0){
        pthread_cond_signal(&pool->cond);
      }
      pthread_mutex_unlock(&pool->mut);
      break;    //退出循環
    }

    //等待超時
    if(timeout == 1 && pool->head == NULL){
      printf("thread:0x%0x waiting timeout\n",pthread_self());
      pool->workThreads--;
      pthread_cond_signal(&pool->cond);
      pthread_mutex_unlock(&pool->mut);
      break; 
    }
    
    //解鎖
    pthread_mutex_unlock(&pool->mut);
  }

  printf("thread: 0x%0x is exiting!\n", (int)pthread_self());
  return NULL;

}

void thread_init(Pool* pool, int max)
{
  //初始化條件變量和互斥
  if(pthread_mutex_init(&pool->mut, NULL) != 0)
  {
    perror("mutex_init");
    return;
  }
  if(pthread_cond_init(&pool->cond, NULL) != 0)
  {
    perror("cond_init");
    return;
  }
  
  //初始化線程池
  pool->workThreads = 0;
  pool->freeThreads = 0;
  pool->maxThreads = max;

  pool->head = NULL;
  pool->tail = NULL;
  pool->destroy = 0;
 
}


void thread_add(Pool* pool, FUNC_POINT func, void* url)
{
  //生成一個新任務
  
  printf("新任務!\n");

  Task* newTask = (Task*)malloc(sizeof(Task));
  newTask->Handler = func;
  newTask->url = url;
  newTask->next = NULL;

  //把新任務添加到任務隊列
  pthread_mutex_lock(&pool->mut);
  
         //如果當前任務隊列是空的
  if(pool->head == NULL)
    pool->head = newTask;
  else   //否則,新的任務放在隊列最後
    pool->tail->next = newTask;

  pool->tail = newTask;   //更新尾指針
  
  //喚醒空閒線程
  if(pool->freeThreads > 0){
        // 喚醒
    pthread_cond_signal(&pool->cond);
  }else if((pool->workThreads) < (pool->maxThreads)){
        // 創建新線程
    printf("創建線程\n");

    pthread_t id;
    pthread_create(&id, NULL, thread_Handler, pool);
    pool->workThreads++;
  }

  pthread_mutex_unlock(&pool->mut);
}

void thread_destroy(Pool* pool)
{
    if(pool->destroy == 1)//如果該線程池已經銷燬過了,直接退出
      return;
    //銷燬線程池
    printf("%d workThreads\n",pool->workThreads);
    pthread_mutex_lock(&pool->mut);
    if(pool->maxThreads > 0){
      if(pool->freeThreads > 0){
        pthread_cond_broadcast(&pool->cond);//喚醒所有線程,檢查是否還有工作需要完成
      }
      while(pool->workThreads > 0){
        pthread_cond_wait(&pool->cond, &pool->mut); //如果有在工作的線程,需要等待
      }
    }

    pool->destroy = 1;//到此處說明所有線程已經完成任務,workThreads=0。退出標誌設爲一
    
    int err;
    err =  pthread_mutex_unlock(&pool->mut);
    printf("err=%d\n",err); 
    err = pthread_mutex_destroy(&pool->mut);
    printf("err=%d,互斥量銷燬\n",err);
    err = pthread_cond_destroy(&pool->cond);
    printf("err=%d,條件變量銷燬\n",err);
    printf("線程池已銷燬\n");

}

threadpool.c

#include "pthreadpool.h"
/*
 *我的實現思路:
 *線程池結構體是Pool,配置活動線程數是通過main函數參數傳入的
 *線程池內維護了一個任務隊列,,每個任務是一個結構體,成員是任務處理函數的地址,參數,和指向下一個任務的指針。
 *活動的線程依次從任務隊列取任務,
 *每個線程取到任務之後執行任務處理函數,該函數fork()一個子進程,由子進程執行程序替換,
 *完成工作的線程在任務隊列爲空的情況下會等到10秒,減少工作線程數量,解鎖,然後線程就退出,
 */
/*
 *解決線程池的銷燬工作:(這一次解決了前一次存在的問題:1,線程沒有完成任務情況下,main函數退出
                                                       2,在線程池銷燬函數中陷入死循環)
 *
 * 解決方法:(這裏在參考了CSDN()[原出處](https://blog.csdn.net/m0_38126105/article/details/79251832)上一個人實現線程池時,對線程池銷燬的做法)
 * 線程銷燬函數在main函數中主動調用,在銷燬函數內等待所有線程完成任務都成爲空閒線程,並且
 * workthreads==0時纔會退出。解決了main函數提前退出的問題。
 */
char URL[][100] = {
  {"https://dl.softmgr.qq.com/original/Browser/QQBrowser_Setup_Qqpcmgr_10.4.3587.400.exe"},
  {"https://dl.softmgr.qq.com/original/Browser/Firefox_Setup_68.0.1_bzb32.exe"},
  {"https://dl.softmgr.qq.com/original/Development/npp.7.7.1.Installer.exe"},
  {"https://sm.myapp.com/original/Development/epp500_0651_64bit-5.0.651.0.exe"},
  {"https://sm.myapp.com/original/Download/fhsetup_8843-3.0.2.exe"},
  {"https://dl.softmgr.qq.com/original/Compression/BANDIZIP-SETUP_6.24.0.1.EXE"},
  {"https://dl.softmgr.qq.com/original/Compression/7z1900-x64.exe"},
  {"https://sm.myapp.com/original/Compression/SpeedZipSetupV2.1.6.6.exe"},
  {"https://sm.myapp.com/original/Compression/WinZip-cn_20.0.12033.exe"},
  {"https://dl.softmgr.qq.com/original/Compression/winrar-x64-571scp.exe"}
  
};

void* fun(void* arg)//任務處理函數
{
 
  pid_t id;
  if((id = fork())<0){
    perror("fork");
    return NULL;
  }else if(id == 0){
    execlp("wget", "wget", "-q", arg, NULL);
  }else{
    wait(NULL);
  }

  free(arg);
  return NULL;
}
//*******************************************************//
//**********************main函數*************************//
//*******************************************************//
int main(int argc, char* argv[])
{
  if(argc != 2){
    printf("Error:[./pthreadpool] [threadnums]\n");
    return 1;
  }

  int threadNums;
  threadNums = atoi(argv[1]);

  Pool pool;
  thread_init(&pool, threadNums);
  int i;
  for(i=0; i<10; ++i)
  {
    char* ret = (char*)malloc(sizeof(char)*100);
    memcpy(ret, URL[i], 100); 
    thread_add(&pool, fun, ret);
  }
 
  signal(SIGCHLD, NULL);
  thread_destroy(&pool);
  printf("main is over!\n");
  return 0;
}

相關問題處理分析:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章