<process.h>以及文件狀態(tài)需要用到的<sys/stat.h>n = CPU's core * 2 - 1。讓我們來(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í)際上就是判斷兩個(gè)文件的最后修改時(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 的編寫
backup 和 show_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 */
fileVec,可以被任何線程訪問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;
}
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ā)生什么問題?一直以來(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)中 */
}
Queue.c)中的一些代碼,需要用關(guān)鍵段進(jìn)行修飾,防止資源爭(zhēng)奪。其他方面沒有太多需要修改的CopyFile CreateDirectory FindFirstFile FindNextFile,是核心的功能函數(shù)。PushBack和PopFront兩個(gè)操作上可以不必?fù)?dān)心資源爭(zhēng)奪。防止在編寫程序的時(shí)候粗心大意忘記了保護(hù)。