线程池可以有效减少线程创建和销毁的的开销,同时简化编程操作。如果在项目中会频繁使用使用线程,一般会自己实现一个线程池,现有的比较好的线程池实现是ACE和glib。有时间的话看看这些线程池的具体实现,相信对linux线程的理解会上升一个档次。不过这次讲的主要是如何使用现有glib库在自己的程序中方便地使用多线程。
我们这次的工作是先从服务器的一个目录下载文件fulllist.txt,该文件中每一行都是一个文件名,我们再从服务器的一个目录下载这些文件到本地。这些文件都是以http协议的格式下载的。由于文件有几千个,单进程下载太慢,所以使用多线程,就是想初步了解一下多线程的大致使用和glib库线程池的使用方法。
例子代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERV_IP "XXXXX"
#define SERV_PORT 80
#define MAX_FILE_PATH 256
#define LIST_FILE_PATH "xxxxxx"
#define XML_FILE_PATH "xxxxxx"
#define MAX_THREAD_NUM 10
void getfile(gpointer path_file,gpointer data);
/* 用于调试记录实际下载文件个数*/
//pthread_mutex_t g_file_count_lock;
//int g_file_count=0;
pthread_mutex_t g_file_path_lock;
/* get file name from file path. */
/* etc. the file path is "/epg/videos/8.xml", so the name we get is 8.xml.*/
char * get_file_name(const char * file_path){
char *ptr1,*ptr2;
ptr1=file_path;
while(*ptr1!='\0'){
if(*ptr1=='/') ptr2=ptr1;
ptr1++;
}
ptr2++;
return ptr2;
}
int main(int argc,char **argv){
FILE *fp=NULL;
char *ptch;
char file_path[MAX_FILE_PATH];
char file_name[MAX_FILE_PATH];
GThreadPool *thread_pool;
int count=0;
/* initialize the global mutex */
pthread_mutex_init(&g_file_path_lock,NULL);
getfile(LIST_FILE_PATH,NULL);
fp=fopen("fulllist.txt","r");
g_thread_init(NULL);
thread_pool=g_thread_pool_new(getfile,NULL,MAX_THREAD_NUM,TRUE,NULL);
if(thread_pool==NULL) {
printf("g_thread_pool_new() error!\n");
exit(0);
}
while(fgets(file_name,MAX_FILE_PATH,fp)!=NULL){
count++;
/* get rid of the last '\n' or '\r' in file_name, */
/* because the function fgets() do read it. */
ptch=file_name;
while(*ptch!='\n'&&*ptch!='\r')
ptch++;
*ptch='\0';
/* befor we change the path_file, we check if weather another is */
/* using it. */
pthread_mutex_lock(&g_file_path_lock);
/* get the complete filepath */
strcpy(file_path,XML_FILE_PATH);
strcat(file_path,file_name);
strcat(file_path,".xml");
g_thread_pool_push(thread_pool,(gpointer*)file_path,NULL);
}
g_thread_pool_free(thread_pool,0,1);
fclose(fp);
/* 用于调试,输出fulllist.txt中的文件数目和最后线程实际创建的文件数目,看是否相同 */
//printf("real: %d get: %d",count,g_file_count);
return 0;
}
void getfile(gpointer file_path,gpointer user_data){
int sockfd;
char buffer[1024];
struct sockaddr_in server_addr;
int nbytes;
/* we can't use the parameter file_path passed to this function,
// because this is a function will be exec in many threads asy,
// we use a mutex to protect the thread_shared varibale file_path,
// we unlock it after we copy it to another variable locally defined.
*/
char thread_file_path[MAX_FILE_PATH];
char* file_name;
FILE * fp;
char request[1024];
int send, totalsend;
int i;
strncpy(thread_file_path,file_path,MAX_FILE_PATH);
pthread_mutex_unlock(&g_file_path_lock);
file_name=get_file_name((char *)thread_file_path);
/* 客户程序开始建立 sockfd描述符 */
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)/*建立SOCKET连接*/
{
fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
goto end;
}
/* 客户程序填充服务端的资料 */
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(SERV_PORT);
inet_pton(AF_INET,SERV_IP,&server_addr.sin_addr);
/* 客户程序发起连接请求 */
if(connect(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)/*连接网站*/
{
fprintf(stderr,"Connect Error:%s\a\n",strerror(errno));
goto end;
}
/* 构建符合格式的request */
sprintf(request, "GET /%s HTTP/1.1\r\nAccept: */*\r\nAccept-Language: zh-cn\r\n\
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)\r\n\
Host: %s:%d\r\nConnection: Close\r\n\r\n", (char *)thread_file_path, SERV_IP, SERV_PORT);
printf("%s", request);/*准备request,将要发送给主机*/
/*发送http请求request*/
send = 0;totalsend = 0;
nbytes=strlen(request);
while(totalsend < nbytes) {
send = write(sockfd, request + totalsend, nbytes - totalsend);
if(send==-1) {printf("send error!%s\n", strerror(errno));exit(0);}
totalsend+=send;
printf("%d bytes send OK!\n", totalsend);
}
fp = fopen(file_name, "a");
if(!fp) {
printf("create file error! %s\n", strerror(errno));
goto end;
}
/*
//用于调试
//pthread_mutex_lock(&g_file_count_lock);
//g_file_count++;
//pthread_mutex_unlock(&g_file_count_lock);
*/
//printf("\nThe following is the response header:\n");
i=0;
/* 连接成功了,接收http响应,response */
while((nbytes=read(sockfd,buffer,1))==1)
{
if(i < 4) {
if(buffer[0] == '\r' || buffer[0] == '\n') i++;
else i = 0;
//printf("%c", buffer[0]);/*把http头信息打印在屏幕上*/
}
else {
fwrite(buffer, 1, 1, fp);/*将http主体信息写入文件*/
i++;
if(i%1024 == 0) fflush(fp);/*每1K时存盘一次*/
}
}
fclose(fp);
/* 结束通讯 */
close(sockfd);
end:
;
}
注. 红色的是涉及使用glib线程池的代码,蓝色的是涉及使用互斥量控制对多线程共享变量访问的代码。
编写时碰到的问题:
1. 包含glib库,除了程序中使用#include , 编译选项要加上`pkg-config --cflags --libs glib-2.0` , 如果直接使用 -I/usr/include/glib2.0/glib/ 会报出很多glib.h包含文件的错误.
2. 使用g_thread_init要加上编译选项`pkg-config --cflags --libs gthread-2.0`.否则会出现错误"g_thread_supported() failed" 还有一堆形如 "g_thread_pool_push: assertion 'real' failed " 的错误.
3. 执行线程的函数出错或者结束时应该是不能调用exit、return、pthread_exit之类的函数,
我在出错时使用goto 到达程序末尾。 这只是我个人观点,没找到具体文档,不知道对不对。
4. 错误:"GThread-Error:**The supplied thread function vector is invalid".
原因是提供的线程执行函数不符合g_thread_pool_new()函数的参数要求。
普通的pthread中线程函数原型要求是 void *(*func)(void),
而glib线程池要求的线程函数原型是 void (*func)(gpointer userdata,gpointer data).
阅读(4923) | 评论(0) | 转发(0) |