在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ C/ 0x0F-多線程備份
0x0E-單線程備份(下)
0x11-套接字編程-1
0x05-C語(yǔ)言指針:(Volume-1)
0x13-套接字編程-HTTP服務(wù)器(1)
0x0C-開始行動(dòng)
C 語(yǔ)言進(jìn)階
第一部分
0x05-C語(yǔ)言指針(Volume-2)
0x08-C語(yǔ)言效率(下)
0x07-C語(yǔ)言效率(上)
0x04 C代碼規(guī)范
0x0F-多線程備份
0x05-C語(yǔ)言變量
第四部分
0x16-套接字編程-HTTP服務(wù)器(4)
0x0D-單線程備份(上)
總結(jié)
0x01-C語(yǔ)言序言
0x15-套接字編程-HTTP服務(wù)器(3)
0x14-套接字編程-HTTP服務(wù)器(2)
0x17-套接字編程-HTTP服務(wù)器(5)
第三部分
我的C語(yǔ)言
0x06-C語(yǔ)言預(yù)處理器
0x09-未曾領(lǐng)略的新風(fēng)景
0x0A-C線程和Glib的視角
第二部分
0x10-網(wǎng)絡(luò)的世界
0x12-套接字編程-2
0x03-C代碼
0x0B-C語(yǔ)言錯(cuò)誤處理

0x0F-多線程備份

0x0F-多線程備份

寫在最前方

  • 到現(xiàn)在為止我們有了一開始的遍歷模型(show_structure)隊(duì)列模型(queue)
  • 現(xiàn)在我們需要做的就是將他們?nèi)诤显谝黄?,并且通過(guò)多線程將其驅(qū)動(dòng)。
  • 以下將會(huì)用到Windows APIWindows線程庫(kù)<process.h>以及文件狀態(tài)需要用到的<sys/stat.h>
  • 對(duì)于一個(gè)多線程的備份程序而言,可以使用一個(gè)十分清晰的方式來(lái)實(shí)現(xiàn),通俗的話來(lái)說(shuō)就是,一個(gè)線程在不斷將路徑模型壓入隊(duì)列中,其他 n個(gè)線程 不斷地從這個(gè)隊(duì)列中彈出路徑,實(shí)行復(fù)制n = CPU's core * 2 - 1。
  • 其次我們需要實(shí)現(xiàn)的是類似增量備份的效果,即有改變的文件才需要重新復(fù)制,或者新增的才需要復(fù)制。
  • 剩下的就是實(shí)現(xiàn)兩個(gè)供線程調(diào)用的函數(shù)(這個(gè)函數(shù)有特殊),一個(gè)入隊(duì),一個(gè)出隊(duì)
  • 之所以選擇Visual Studio還有另一個(gè)原因,它的某些必要函數(shù)是可以開啟支持線程安全的,這個(gè)概念我不作解釋,記得在屬性中查看是否開啟/MT(多線程)

寫在中間

  • 讓我們來(lái)施展一個(gè)很久不用的魔法 3, 2, 1!

      static queue filesVec;    /* 隊(duì)列主體 */
      HANDLE vecEmpty, vecFull; /* 兩個(gè) Semaphore */
      HANDLE pushThread;  /* 將路徑加入隊(duì)列中的線程 */
      HANDLE copyThread[SELF_THREADS_LIMIT]; /* 將路徑彈出隊(duì)列并復(fù)制的線程 */
      CRITICAL_SECTION inputSec, testSec, manageSec; /* 關(guān)鍵段或臨界區(qū) */
    
      /* 計(jì)算時(shí)間 */
      static clock_t start, finish; /* 備份開始,結(jié)束時(shí)間 */
      static double Total_time;    /* 計(jì)算備份花費(fèi)時(shí)間 */

    這些東西,都被寫在了backup.c中,作為全局變量,暫時(shí)先不管其中看不懂的部分,可能到現(xiàn)在為止,大家都已經(jīng)迷糊了。但是沒關(guān)系,因?yàn)檫€沒說(shuō)過(guò)所以迷糊。繼續(xù)往下

  • 從小事做起,先實(shí)現(xiàn)一個(gè)簡(jiǎn)單的增量備份功能
  • 實(shí)際上就是判斷兩個(gè)文件的最后修改時(shí)間是否一致

    • 實(shí)現(xiàn)判斷目的路徑上的文件是否存在
    • 如果存在,則再次判斷源路徑上的文件和目的路徑上的文件的最后修改時(shí)間是否相同
  • not_changed

      /**
       * @version  1.0 2015/10/03
       * @author   wushengxin
       * @param    dstfile 目的路徑的文件
                   srcfile 源路徑的文件
       * @function 判斷兩個(gè)文件的最后修改時(shí)間是否相同
       */
      static int not_changed(const char * __restrict dstfile, const char * __restrict srcfile)
      {
          struct stat dst_stat, src_stat;
          stat(dstfile, &dst_stat);
          stat(srcfile, &src_stat);
          return dst_stat.st_mtime == src_stat.st_mtime;
      }

    這個(gè)函數(shù)定義在backup.c中,因?yàn)闆]有在頭文件中聲明,所以一定要定義在調(diào)用者的前方。

    這個(gè)函數(shù)比較短小,實(shí)現(xiàn)的功能就是判斷最后修改的時(shí)間是否相同,用到頭文件sys/stat.h

  • 兩個(gè)被線程調(diào)用的函數(shù)

    • 首先是入隊(duì)功能的函數(shù):這個(gè)函數(shù)主要是調(diào)用最后實(shí)現(xiàn)的backup函數(shù),用于遞歸遍歷所給路徑下的所有文件夾,將所有文件路徑轉(zhuǎn)換成路徑模型,壓入隊(duì)中

          /**
           * @version 1.0 2015/10/03
           * @author  wushengxin
           * @param   pSelect 傳入的參數(shù),當(dāng)前為備份的源路徑
           * function 作為線程開始函數(shù)的一個(gè)參數(shù),作用是調(diào)用 backup 函數(shù)
           */
          static unsigned int __stdcall callBackup(void * pSelect)
          {
              char* tmpPath = (char*)pSelect; /* 源路徑 */
              start = clock(); /* 開始計(jì)時(shí) */
              backup(tmpPath, get_backup_tpath());
              return 0;
          }
      這個(gè)函數(shù)定義在`backup.c`中,因?yàn)闆]有在頭文件中聲明,所以一定要定義在調(diào)用者的前方。
      
      其中參數(shù)`pSelect`的用法就像是可以接受任何類型的**泛型函數(shù)**,只不過(guò)需要自己提前知道類型,這個(gè)技術(shù)也被用于C語(yǔ)言的面向?qū)ο螅?*可用于隱藏成員變量和成員函數(shù)**。最后可能會(huì)稍微介紹一下。
      
      - 其次是出隊(duì)的函數(shù):這個(gè)函數(shù)的功能比較多,首先等待隊(duì)列非空(vecFull)的**信號(hào)(Semaphore)**,得到信號(hào)之后就彈出一個(gè)**路徑模型**,進(jìn)行復(fù)制操作,并且負(fù)責(zé)把路徑模型使用的內(nèi)存釋放,在此釋放一個(gè)隊(duì)列空的信號(hào),進(jìn)入下一個(gè)循環(huán)。
      
              static unsigned int __stdcall callCopyFile(void * para)
              {
                  DWORD    isExit   = 0;         /* 判斷入隊(duì)線程是否還存在 */
                  queue*   address  = &filesVec;
                  combine* localCom = NULL;
                  int      empty    = 0;
                  while (1)
                  {
                      char * dst_path = NULL;
                      char * src_path = NULL;
                      EnterCriticalSection(&testSec);
                      GetExitCodeThread(pushThread, &isExit); /* 查看入隊(duì)的線程是否已經(jīng)結(jié)束 */
                      empty = address->empty; /* 查看此時(shí)隊(duì)列是否為空 */
                      LeaveCriticalSection(&testSec);
                      if (isExit != STILL_ACTIVE && empty) /* STILL_ACTIVE 代表還在運(yùn)行 */
                      {
                          puts("Push Thread is End!");
                          break;   
                      }
      
                      isExit = WaitForSingleObject(vecFull, 3000); /* 設(shè)定一個(gè)等待時(shí)間,以防死鎖 */
                      if (isExit == WAIT_TIMEOUT)
                      {
                          fprintf(stderr, "Copy Thread wait time out!\n");
                          continue;
                      }
      
                      EnterCriticalSection(&manageSec); /* 這個(gè)關(guān)鍵段的添加十分重要,是讀取時(shí)候的核心 */
                      if (!(localCom = filesVec.PopFront(address))) /* 每次彈出時(shí)一定要防止資源爭(zhēng)奪帶來(lái)的沖突 */
                          continue;
                      LeaveCriticalSection(&manageSec);
      
                      dst_path = localCom->dst_to_path; /* 空間局部性 */
                      src_path = localCom->src_from_path;
      
                      if (CopyFileA(src_path, dst_path, FALSE) == 0) /* 顯式使用 CopyFileA 函數(shù),而不是使用 CopyFile 宏 */
                      {
                          EnterCriticalSection(&inputSec);
                          if (ERROR_ACCESS_DENIED == GetLastError())
                          {
                              fprintf(stderr, "\nThe File has already existed and is HIDDEN or ReadOnly! \n");
                              fprintf(stderr, "Copy File from %s Fail!\n", src_path);
                          }
                          else if (ERROR_ENCRYPTION_FAILED == GetLastError())
                          {
                              fprintf(stderr, "\nThe File is Encrypted(被加密), And Can't not be copy\n");
                              fprintf(stderr, "Copy File from %s Fail!\n", src_path);
                          }
                          LeaveCriticalSection(&inputSec);
                      }
                      Free_s(src_path);
                      Free_s(dst_path);
                      Free_s(localCom);
                      ReleaseSemaphore(vecEmpty, 1, NULL); /* 是放一個(gè)信號(hào)量 */
                  }/* while (1) */        
                  return 0;
              }
      這個(gè)函數(shù)看似很長(zhǎng),實(shí)際上大半實(shí)在做判斷,而不是在做拷貝,真正做拷貝的是在中間部分的`WaitForSingleObject`函數(shù)之后才開始的
      
      - 解釋一下
          - 因?yàn)樵诖颂幉⒉皇嵌嗑€程的基礎(chǔ)文章,而是假設(shè)你有基礎(chǔ),如果沒有,可以前往一個(gè)地方**CSDN作者**:`MoreWindows`,它的多線程文章十分通俗易懂
          - 這次我們提到的多線程概念有
              - `Semaphore(信號(hào)量)`,使用的一個(gè)類似多個(gè)互斥量的概念
              - `CRITICAL_SECTION(關(guān)鍵段/臨界區(qū))`,作用和鎖相同,但是某些情況下(**粗心**)不能很好的保護(hù)資源不被爭(zhēng)奪,不能再進(jìn)程間共享
              - `Mutex(互斥量)`,用了**非遞歸的**鎖一定能保護(hù)好資源不被爭(zhēng)奪。但是教`CRITICAL_SECTION`的開銷要大。
              - 其他信息請(qǐng)參看[那位的博客](http://blog.csdn.net/morewindows/article/details/17488865)。
          - 假設(shè)你已經(jīng)具備了多線程的基礎(chǔ)。
      - 那么講解一下思路:
          - 首先可以將線程當(dāng)成這個(gè)函數(shù),那么按順序執(zhí)行的結(jié)果就是,進(jìn)入循環(huán)(好吧廢話)
          - 其次我們需要時(shí)刻**警惕**,入隊(duì)線程是否已經(jīng)結(jié)束?并且結(jié)束的話隊(duì)列是否為空?如果兩個(gè)條件**同時(shí)成立**,那么就結(jié)束**本線程**,任務(wù)結(jié)束。
          - 只要任意的條件不符合,就代表本線程的任務(wù)還要繼續(xù),那么就在原地等待**信號(hào)**,一個(gè)隊(duì)列非空(`vecFull`)的信號(hào)。
          - 一旦接受到信號(hào),就證明隊(duì)列中有**路徑模型**可以被本線程彈出,就開始彈出路徑模型,此時(shí)一定要記住用**關(guān)鍵段或者鎖**給彈出操作做保險(xiǎn)。
          - 這里提一句,互斥量(**Mutex**)比關(guān)鍵段(**Critical Section**)**要可靠**,但開銷更大
          - 彈出之后就是調(diào)用**API**進(jìn)行復(fù)制,隨后釋放堆上的空間,最后釋放一個(gè)信號(hào),代表隊(duì)列中的元素被我彈出了一個(gè)。
          - 進(jìn)入下一次循環(huán)
      - 可以將其中的`stderr`換成文件流,將錯(cuò)誤信息輸入到文件中,而不是屏幕上,以保存錯(cuò)誤信息不至于丟失。
  • 下面開始主體函數(shù) backup 的編寫

    • 由于此次的代碼過(guò)長(zhǎng),所以不放上代碼,一切代碼都可以到我的Github倉(cāng)庫(kù)下載。
    • 講解思路
    • 首先 backupshow_structure 最大的不同便在于后者不需要保存路徑模型,而是直接使用。
    • 故我們只需要在 show_structure 的路徑變量中,添加一個(gè)目的路徑的參數(shù)就行。即backup函數(shù)中的主要參數(shù)變?yōu)?strong>三個(gè):

          /* backup.c : backup */
          char * from_path_buf = make_path(path); /* 源路徑 */
          char * to_path_buf   = make_path(bpath); /* 目的路徑 */
          char * find_path_buf = make_path(path); /* 用于 Windows API FindFirstFile */
    • 首先我們擁有一個(gè)靜態(tài)全局的隊(duì)列 fileVec,可以被任何線程訪問
    • 緊接著我們構(gòu)造了兩個(gè)動(dòng)作,壓入(backup),彈出(callCopyFile),backup是用callBackup調(diào)用。
    • 二級(jí)界面中,當(dāng)我們選擇第一個(gè)選項(xiàng)開始備份后,我們選擇在此時(shí)獲得源路徑,并將之通過(guò)線程創(chuàng)建函數(shù) _beginthreadex 傳遞給 callBackup,進(jìn)而傳遞給backup函數(shù),開始?jí)喝肴蝿?wù)。

          /**
           * @version 1.0 2015/10/03
           * @author  wushengxin
           * @param   pSelect 傳入的參數(shù),可以是NULL
           * function 作為線程開始函數(shù)的一個(gè)參數(shù),作用是調(diào)用 backup 函數(shù)
           */
          static unsigned int __stdcall callBackup(void * pSelect)
          {
              char* tmpPath = (char*)pSelect;
              start = clock();
              backup(tmpPath, get_backup_topath());
              return 0;
          }
    • 在創(chuàng)建并完成壓入線程之后,開始創(chuàng)建拷貝線程,之所以這么安排,是因?yàn)閴喝氲牟僮鞅囟ū瓤截惖囊?,且我們一開始便將信號(hào)量vecEmpty初始化為 20,這是因?yàn)橐婚_始的隊(duì)列是空的,需要壓入線程先開始行動(dòng)。
    • 這里需要提到的是 _beginthreadex 函數(shù),還有一個(gè)與它相似的函數(shù)是 _beginthread,兩者之間的區(qū)別在于,前者參數(shù)更多,前者類似POSIX里的非分離式線程屬性,前者使用完需要手動(dòng)銷魂,前后者調(diào)用的函數(shù)修飾不一樣,什么意思?如果下面這個(gè)代碼使用后者創(chuàng)建會(huì)發(fā)生什么問題?
    • 想想分離式線程的特點(diǎn),就是自動(dòng)釋放所有的資源,這就會(huì)導(dǎo)致,如果前一個(gè)線程比自己創(chuàng)建的還快完成任務(wù),那么自己就可能用到它的句柄,這就可能會(huì)造成錯(cuò)誤。而如果前者的話,由程序員稍后自己釋放銷毀句柄,能保證一定不會(huì)出現(xiàn)這種現(xiàn)象。
    • 一直以來(lái)都是使用前者。

          /* backup.c : backup */
          pushThread = (HANDLE)_beginthreadex(NULL, 0, callBackup, (void*)tmpPath, 0, NULL); /* 壓入線程 */
          for (int i = 0; i < SELF_THREADS_LIMIT; ++i)
          {
              copyThread[i] = (HANDLE)_beginthreadex(NULL, 0, callCopyFile, NULL, 0, NULL); /* 拷貝線程 */
          }
    • 在壓入的過(guò)程中,唯一需要注意的就是在壓入fileVec的時(shí)候,一定要防止資源競(jìng)爭(zhēng)(同樣適用在復(fù)制過(guò)程中的彈出操作),通過(guò)信號(hào)量可以有效防止多于1個(gè)以上的線程同時(shí)訪問fileVec

          /* backup.c : backup */
          if(is Directory)
              { ... }
          else /* 是一個(gè)文件 */
          {
              strcat(tmp_from_file_buf, fileData.cFileName);
              strcat(tmp_to_file_buf, fileData.cFileName);
              if (_access(tmp_to_file_buf, 0) == 0) /*如果目標(biāo)文件存在*/
              {
                  if (is_changed(tmp_from_file_buf, tmp_to_file_buf))
                  {
                      rele_path(tmp_from_file_buf);
                      rele_path(tmp_to_file_buf);
                      continue;  /*如果目標(biāo)文件與源文件的修改時(shí)間相同,則不需要入隊(duì)列*/
                  }
                  fprintf(stderr, "File : %s hast changed!\n", tmp_from_file_buf);
              }
              else
                  fprintf(stderr, "Add New File %s \n", tmp_from_file_buf);
              /* 使用信號(hào)量防止競(jìng)爭(zhēng) */
              WaitForSingleObject(vecEmpty, INFINITE);
              EnterCriticalSection(&manageSec);
              filesVec.PushBack(&filesVec, tmp_from_file_buf, tmp_to_file_buf);
              LeaveCriticalSection(&manageSec);
              ReleaseSemaphore(vecFull, 1, NULL);
          }
    • 在復(fù)制的過(guò)程中,十分有可能出現(xiàn)壓入線程結(jié)束,但是拷貝線程卻停留在等待信號(hào)的階段,這就要求我們必須設(shè)定一個(gè)等待的時(shí)間,超時(shí)則重新檢測(cè)是否是壓入線程結(jié)束且隊(duì)列空。這一點(diǎn)十分重要,可以自己思考一下。

          /* backup.c : callCopyFile */
          EnterCriticalSection(&testSec);
          GetExitCodeThread(pushThread, &isExit); /* 查看入隊(duì)的線程是否已經(jīng)結(jié)束 */
          empty = address->empty; /* 查看此時(shí)隊(duì)列是否為空 */
          LeaveCriticalSection(&testSec);
          if (isExit != STILL_ACTIVE && empty) /* STILL_ACTIVE 代表還在運(yùn)行 */
          {
              puts("Push Thread is End!\n");
              break;   
          }
      
          isExit = WaitForSingleObject(vecFull, 3000); /* 設(shè)定一個(gè)等待時(shí)間,以防死鎖 */
          if (isExit == WAIT_TIMEOUT)
          {
              fprintf(stderr, "Copy Thread wait time out!\n");
              continue; /* 所有代碼都在一個(gè) while(1)中 */
          }
    • 當(dāng)所有線程都退出就代表任務(wù)完成,要銷毀一系列相關(guān)參數(shù)。

寫在最后

  • 添加了多線程以后,前方有一些原始代碼是需要修改的才能使用,比如隊(duì)列模型(Queue.c)中的一些代碼,需要用關(guān)鍵段進(jìn)行修飾,防止資源爭(zhēng)奪。其他方面沒有太多需要修改的
  • 完整代碼被我放在我的Github倉(cāng)庫(kù)

簡(jiǎn)單總結(jié)

  • 使用的 Windows APICopyFile CreateDirectory FindFirstFile FindNextFile,是核心的功能函數(shù)。
  • 在此處,可以換一個(gè)思路思考一下,是否可以對(duì)容器隊(duì)列,進(jìn)行線程安全保護(hù),從而不必在主代碼中一直使用關(guān)鍵段進(jìn)行保護(hù)?至少在PushBackPopFront兩個(gè)操作上可以不必?fù)?dān)心資源爭(zhēng)奪。防止在編寫程序的時(shí)候粗心大意忘記了保護(hù)。