【编程之美】双线程高效下载
一,题目
网络上下载数据,然后存储到硬盘上。简单做法是:先下载一块然后写到硬盘,然后再下载,再写到硬盘上。
缺点:需要先下载完才能写入硬盘,下载和写是串行操作。
改进:让两个线程并行进行,设置缓冲区,采用信号量的形式。
下载线程,只要缓冲区有空余就下载,下载完成之后告诉写线程缓冲区有数据了。
写线程,只要缓冲区有数据就写入,写完后告诉下载线程缓冲区有空闲了。
二,核心源码
//downloads a block from Internet sequentially in each call //return true, if the entire file is downloaded, otherwise false. bool GetBlockFromNet(Block* out_block); //writes a block to hard disk bool WriteBlockToDisk(Block* in_block); class Thread { public: Thread(void (*work_func)()); ~Thread(); void Start(); void Abort(); }; class Semaphore { public: Semaphore(int count,int max_count); ~Semaphore(); void Unsignal(); void Signal(); }; class Mutex { public: WaitMutex(); ReleaseMutex(); }; //---------------------------------------------------- //1.确定使用信号量,而非互斥量,保证并行操作 //2.当缓冲区并不满并且下载没结束时,下载线程运行 //3.当缓冲区并不空并且下载没结束时,存储线程运行 #define MAX_COUNT 1000 Block g_Buffer[MAX_COUNT]; //缓冲区数组,模拟循环队列 Thread g_Download(ProcA); Thread g_Write(ProcB); //一开始缓冲区空间为MAX_COUNT,整个缓冲区可供下载的数据填充 Semaphore ForDownload(MAX_COUNT,MAX_COUNT); //一开始缓冲区无数据可供存储 Semaphore ForWrite(0,MAX_COUNT); //下载任务是否完成 bool isDone; //下载的数据从缓冲区的哪个地方开始填充 int in_index; //存储的数据从缓冲区的哪个地方开始提取 int out_index; void ProcA()//下载线程 { while(true) { //首先取得一个空闲空间,以便下载数据填充 ForDownload.Unsignal(); //填充 isDone=GetBlockFromNet(g_Buffer+in_index); //更新索引 in_index=(in_index+1)%MAX_COUNT; //提示存储线程可以工作 ForWrite.Signal(); //当任务全部下载完成,进程就可以结束了 if(isDone) break; } } void ProcB()//写入线程 { while(true) { //查询时候有数据可供存储 ForWrite.Unsignal(); //存储 WriteBlockToDisk(g_Buffer+out_index); //更新索引 out_index=(out_index+1)%MAX_COUNT; //将空闲空间还给缓冲区 ForDownload.Signal(); //当任务全部下载完成,并且所有的数据都存储到硬盘中,进程才可以结束 if(isDone&&in_index==out_index) break; } } int main() { isDone=false; in_index=0; out_index=0; g_Download.Start(); g_Write.Start(); }