1. 程式人生 > >【Linux】執行緒併發拷貝程式

【Linux】執行緒併發拷貝程式

據說大連某211高校的李教授越來越重口,不僅延續要求他所帶的每一個本科班,都要寫一份執行緒併發拷貝程式的傳統,而且還開始規定不能用Java語言寫作,導致我之前寫的《【Java】執行緒併發拷貝程式》(點選開啟連結)作廢。所有李教授旗下的學生,必須在毫無圖形介面的Linux系統,用裡面vi去寫作。這更讓莘莘學子們感覺本來頭來就不光明的天空更加黑暗起來。

更重要的是,若干年過去了,網上對其的研究與資料,依舊是少數,依舊是那份流傳已久,以訛傳訛的C語言版。

雖然這個程式毫無研究價值,但是本著治病救人,同時藉此深入研究LinuxC的執行緒程式設計機制,我再一次完成了這份,在Linux用最最最純種C語言完成的執行緒併發拷貝程式。

如下圖,搞了3個執行緒在Linux系統下完成資料夾A中的所有內容到空空是也資料夾B的複製。同時按照李教授的喜好,在前面補個字首,也就是重新命名目標資料夾。


可能有些同學說我上面的圖形化的Linux系統與無圖形化的Linux是不同的。

其實上述程式碼在各個版本的Linux系統都可以執行的,只是你寫C語言要用《【Linux】vi/vim的使用》(點選開啟連結)去寫,查詢檔案與資料夾,要用cd命令先進入相關的路徑,同時用dir命令讀這個資料夾目錄。要是你覺得vi難用,可以用《【Linux】用Winscp遠端訪問無圖形介面的Linux系統》(點選開啟連結),將你在windows下寫好的程式碼,傳上Linux,再用gcc編譯再執行。

具體程式碼如下:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<dirent.h>//輸出檔案資訊
#include<sys/stat.h>//判斷是否目錄
#include<pthread.h>//使用執行緒

char *source_arr[512];//存放原始檔路徑的陣列
char *destination_arr[512];//存放目標檔案路徑的陣列
int source_arr_index=0;//存放原始檔路徑的陣列的索引,就是for(int i=xx;..;..)那個i
int destination_arr_index=0;//存放目標檔案路徑的陣列的索引
pthread_mutex_t mutex;//宣告一個互斥鎖mutex
int i=0;//多個執行緒函式用到這個i,用於記錄是否複製完畢,因此作為全域性變數處理~

/*字串處理函式*/
int endwith(char* s,char c){//用於判斷字串結尾是否為“/”與“.”
	if(s[strlen(s)-1]==c){
		return 1;
	}
	else{
		return 0;
	}
}
char* str_contact(char* str1,char* str2){//字串連線
	char* result;
	result=(char*)malloc(strlen(str1)+strlen(str2)+1);//str1的長度+str2的長度+\0;
	if(!result){//如果記憶體動態分配失敗
		printf("字串連線時,記憶體動態分配失敗\n");
		exit(1);
	}
	strcat(result,str1);
	strcat(result,str2);//字串拼接
	return result;
}

/*遍歷函式*/
int is_dir(char* path){//判斷是否是目錄
	struct stat st;
	stat(path,&st);
	if(S_ISDIR(st.st_mode)){
		return 1;
	}
	else{
		return 0;
	}
}
void read_folder(char* source_path,char *destination_path){//複製資料夾
	if(!opendir(destination_path)){
		if (mkdir(destination_path,0777))//如果不存在就用mkdir函式來建立
		{
		    printf("建立資料夾失敗!");
		}
	}
	char *path;
	path=(char*)malloc(512);//相當於其它語言的String path="",純C環境下的字串必須自己管理大小,這裡為path直接申請512的位置的空間,用於目錄的拼接
	path=str_contact(path,source_path);//這三句,相當於path=source_path
	struct dirent* filename;
	DIR* dp=opendir(path);//用DIR指標指向這個資料夾
	while(filename=readdir(dp)){//遍歷DIR指標指向的資料夾,也就是檔案陣列。
		memset(path,0,sizeof(path));
		path=str_contact(path,source_path);
		//如果source_path,destination_path以路徑分隔符結尾,那麼source_path/,destination_path/直接作路徑即可 
		//否則要在source_path,destination_path後面補個路徑分隔符再加檔名,誰知道你傳遞過來的引數是f:/a還是f:/a/啊?
		char *file_source_path;
		file_source_path=(char*)malloc(512);
		file_source_path=str_contact(file_source_path,source_path);
		if(!endwith(source_path,'/')){			
			file_source_path=str_contact(source_path,"/");
		}
		char *file_destination_path;
		file_destination_path=(char*)malloc(512);
		file_destination_path=str_contact(file_destination_path,destination_path);
		if(!endwith(destination_path,'/')){			
			file_destination_path=str_contact(destination_path,"/");			
		}		
		//取檔名與當前資料夾拼接成一個完整的路徑
		file_source_path=str_contact(file_source_path,filename->d_name);
		
		if(is_dir(file_source_path)){//如果是目錄
			if(!endwith(file_source_path,'.')){//同時並不以.結尾,因為Linux在所有資料夾都有一個.資料夾用於連線上一級目錄,必須剔除,否則進行遞迴的話,後果無法想象!
				file_destination_path=str_contact(file_destination_path,filename->d_name);//對目標資料夾的處理,取檔名與當前資料夾拼接成一個完整的路徑
				read_folder(file_source_path,file_destination_path);//進行遞迴呼叫,相當於進入這個資料夾進行遍歷~
			}		
		}
		else{//否則,將原始檔於目標檔案的路徑分別存入相關陣列
			//對目標資料夾的處理,取檔名與當前資料夾拼接成一個完整的路徑
			file_destination_path=str_contact(file_destination_path,"字首_");//給目標檔案重新命名,這裡示意如何加個字首~^_^
			file_destination_path=str_contact(file_destination_path,filename->d_name);
			source_arr[source_arr_index]=file_source_path;
			source_arr_index++;
			
	
			destination_arr[destination_arr_index]=file_destination_path;
			destination_arr_index++;
			
		}
	}	
}

/*複製函式*/
void copy_file(char* source_path,char *destination_path){//複製檔案
	char buffer[1024];
	FILE *in,*out;//定義兩個檔案流,分別用於檔案的讀取和寫入int len;
	if((in=fopen(source_path,"r"))==NULL){//開啟原始檔的檔案流
		printf("原始檔開啟失敗!\n");
		exit(1);
	}
	if((out=fopen(destination_path,"w"))==NULL){//開啟目標檔案的檔案流
		printf("目標檔案建立失敗!\n");
		exit(1);
	}
	int len;//len為fread讀到的位元組長
	while((len=fread(buffer,1,1024,in))>0){//從原始檔中讀取資料並放到緩衝區中,第二個引數1也可以寫成sizeof(char)
		fwrite(buffer,1,len,out);//將緩衝區的資料寫到目標檔案中
	}
	fclose(out);
	fclose(in);
}

/*執行緒執行函式*/
void *thread_function(void *arg){
	while(i<destination_arr_index){  
		if(pthread_mutex_lock(&mutex)!=0){//對互斥鎖上鎖,臨界區開始  
			printf("%s的互斥鎖建立失敗!\n",(char *)arg);  
			pthread_exit(NULL);  
		}  
		if(i<destination_arr_index){
			copy_file(source_arr[i],destination_arr[i]);//複製單一檔案
			printf("%s複製%s到%s成功!\n",(char *)arg,source_arr[i],destination_arr[i]);
			i++;
			sleep(1);//該執行緒掛起1秒  
		}  
		else{//否則退出  
			pthread_exit(NULL);//退出執行緒  
		}  
		pthread_mutex_unlock(&mutex);//解鎖,臨界區結束  
		sleep(1);//該執行緒掛起1秒  
	}  
	pthread_exit(NULL);//退出執行緒  
}  

/*主函式*/
int main(int argc,char *argv[]){
	if(argv[1]==NULL||argv[2]==NULL){
		printf("請輸入兩個資料夾路徑,第一個為源,第二個為目的!\n");
		exit(1);
	}
	char* source_path=argv[1];//取使用者輸入的第一個引數
	char* destination_path=argv[2];//取使用者輸入的第二個引數
	DIR* source=opendir(source_path);
	DIR* destination=opendir(destination_path);
	if(!source||!destination){
		printf("你輸入的一個引數或者第二個引數不是資料夾!\n");
	}
	read_folder(source_path,destination_path);//進行資料夾的遍歷
	/*執行緒併發開始*/
	pthread_mutex_init(&mutex,NULL);//初始化這個互斥鎖  
	//宣告並建立三個執行緒  
	pthread_t t1;  
	pthread_t t2;  
	pthread_t t3;  
	if(pthread_create(&t1,NULL,thread_function,"執行緒1")!=0){  
		printf("建立執行緒失敗!程式結束!\n");  
		exit(1);  
	}  
	if(pthread_create(&t2,NULL,thread_function,"執行緒2")!=0){  
		printf("建立執行緒失敗!程式結束!\n");  
		exit(1);  
	}  
	if(pthread_create(&t3,NULL,thread_function,"執行緒3")!=0){  
		printf("建立執行緒失敗!程式結束!\n");  
		exit(1);  
	}
  
	pthread_join(t1,NULL);  
	pthread_join(t2,NULL);  
	pthread_join(t3,NULL);  
	//三個執行緒都完成才能執行以下的程式碼  
	pthread_mutex_destroy(&mutex);//銷燬這個互斥鎖  
	/*執行緒併發結束*/
	return 0;
}

這百來行程式碼,也沒有什麼好說的。

首先這個程式是《【Linux】執行緒互斥》(點選開啟連結)與《【Linux】C語言實現資料夾拷貝》(點選開啟連結)的結合體。裡面涉及的概念,我已經在這兩篇檔案都詳細寫了,這裡就不再大篇幅敘述了。

之後,由於涉及大量路徑的拼接,搞到最後還是《【Linux】純C環境下字串的處理》(點選開啟連結)問題,C語言就是這麼煩,搞個字串,要用到指標、陣列、函式,等各種大量複雜的概念去處理。