编程之美:第一章 1.10双线程高效下载

/*
双线程高效下载:
下载一块数据,写入硬盘,然后再下载,再写入硬盘,不断重复这个过程,直到所有的内容下载完毕为止。能否
对此进行优化?

1假设所有数据块的大小都是固定的。你可以使用一个全局缓存区:
Block g_buffer[BUFFER_COUNT]
2假设两个基本函数已经实现(你可以假定两个函数都能正常工作,不会抛出异常)


bool GetBlockFromNet(Block* out_block);//从网上下载sequentially:从而,在每个调用中都返回true,如果
完整的文件被下载下来,否则为false

bool WriteBlockToDisk(Block* in_block)//写一块数据到硬盘中

为了提高效率,希望设计两个线程,使得下载和写硬盘能够并行执行
线程A:从网络中读取一个数据块,存储到内存的缓存中
线程B:从缓存中读取内容,存储到文件中
请实现如下子程序:
1初始化部分
2线程A
3线程B

如果网络延迟为L1,磁盘I/O延迟为L2,将此多线程与单线程进行比较,分析这个设计的性能问题,并考虑是否还有
其他改进的设计方法?

当时做项目的时候:我是写入到vector中,一旦到时间,就对vector加锁,然后取出数据处理,处理完后清空
然后释放锁,因此起始从取数据到处理数据的确是串行的


分析:
1什么时候算是完成任务?
两个线程必须协同工作,将网络上的数据下载完并存储到硬盘上,线程才能终止
2希望两个线程能尽可能同时工作
如果使用Mutex,下载和存储线程将不能同时工作。因此,Semaphore是更好的选择。
3下载和存储线程工作的必要条件:
如果共享缓存区已经满了,应该暂停下载。如果所有内容全部下载完毕,没必要再下载。
如果缓存区为空,没必要云心存储吸纳成。如果下载结束,存储线程也结束。
4共享缓存区的数据结构
先进先出应该用队列。因为采用了固定的缓冲空间保存下载内容,因此用循环队列。 
*/

/*
关键:
1 生产者消费者问题,需要设置信号量来实现。如果一个程序由k个串行部分组成,则对每个串行部分用一个线程
即可
2 共享缓存区的数据结构
先进先出应该用队列。因为采用了固定的缓冲空间保存下载内容,因此用循环队列。 
3 Thread(void (*work_func)());//实例化一个线程,并设置工作函数
Thread g_threadA(procA);//注意线程里面是用函数指针来构造的
4  void Unsignal()//消费一个信号,则信号数减1,如果计数器变为0,则阻塞当前线程
 {
  _iCount--;
 }
5 Semaphore g_semFull(0,BLOCK_COUNT);//起初设置空缓冲数=缓冲区大小
6 g_semFull.Unsignal();//信号量操作,而不使用加锁操作,能够实现并行程序
7 WriteBlockToDisk(g_buffer + g_outIndex);//如果网络延迟为L1,磁盘I/O延迟为L2,这个多线程执行
  //时间约为Max(L1,L2)参见操作系统之读写时间。单线程时间为L1+L2。如果网络延迟远大于I/O延迟
  //则多个下载线程的设计可进一步改善性能。
8 g_outIndex = (g_outIndex + 1) % BLOCK_COUNT;//形成循环队列
9   if(g_downloadComplete && g_inIndex == g_outIndex)
   //如果已经下载完毕并且缓冲区内容为空(消费者追上生产者,参见循环队列
   //:判空:队头=队尾,判满:队尾+1 = 队头)
  {
   break;
  }
*/

#include <stdio.h>
const int BLOCK_COUNT = 1000;
typedef struct Block
{
}Block;


class Thread
{
public:
 Thread(void (*work_func)());//实例化一个线程,并设置工作函数
 ~Thread();
 void start();
 void abort();
};

class Semaphore
{
public:
 Semaphore(int count,int max_count):_iCount(count),_iMaxCount(max_count){}//初始化信号计数器
 ~Semaphore(){}
 void Unsignal()//消费一个信号,则信号数减1,如果计数器变为0,则阻塞当前线程
 {
  _iCount--;
 }
 void Signal()//增加一个信号,信号数加1
 {
  _iCount++;
 }
private:
 int _iCount;
 int _iMaxCount;
};


class Mutex
{
public:
 void WaitMutex();//阻塞线程,知道释放这个锁
 void ReleaseMutex();//释放锁,让其他线程可以等待进入
};


bool GetBlockFromNet(Block* out_block);
bool WriteBlockToDisk(Block* in_block);
void procA();
void procB();
Thread g_threadA(procA);//注意线程里面是用函数指针来构造的
Thread g_threadB(procB);
Semaphore g_semFull(0,BLOCK_COUNT);//起初设置空缓冲数=缓冲区大小
Semaphore g_semEmpty(BLOCK_COUNT,BLOCK_COUNT);
bool g_downloadComplete;
int g_inIndex = 0;
int g_outIndex = 0;
Block g_buffer[BLOCK_COUNT];
void procA()//生产者
{
 while(true)
 {
  g_semEmpty.Unsignal();
  g_downloadComplete = GetBlockFromNet(g_buffer + g_inIndex);
  g_inIndex = (g_inIndex + 1) % BLOCK_COUNT;//形成循环队列
  g_semFull.Signal();
  if(g_downloadComplete)
  {
   break;
  }
 }
}


void procB()//消费者进程
{
 while(true)
 {
  g_semFull.Unsignal();//信号量操作,而不使用加锁操作,能够实现并行程序
  WriteBlockToDisk(g_buffer + g_outIndex);//如果网络延迟为L1,磁盘I/O延迟为L2,这个多线程执行
  //时间约为Max(L1,L2)参见操作系统之读写时间。单线程时间为L1+L2。如果网络延迟远大于I/O延迟
  //则多个下载线程的设计可进一步改善性能。
  g_outIndex = (g_outIndex + 1) % BLOCK_COUNT;//形成循环队列
  g_semEmpty.Signal();
  if(g_downloadComplete && g_inIndex == g_outIndex)
   //如果已经下载完毕并且缓冲区内容为空(消费者追上生产者,参见循环队列
   //:判空:队头=队尾,判满:队尾+1 = 队头)
  {
   break;
  }
 }
}

 

 

bool GetBlockFromNet(Block* out_block)
{
 return true;
}

bool WriteBlockToDisk(Block* in_block)
{
 return true;
}

void download()
{
 while(true)
 {
  bool isComplete;
  isComplete = GetBlockFromNet(g_buffer);
  WriteBlockToDisk(g_buffer);//然后向硬盘中写数据
  if(isComplete)
  {
   break;
  }
 }
}

void process()
{
 g_downloadComplete = false;
 g_threadA.start();
 g_threadB.start();
 //等待直到线程结束
}

int main(int argc,char* argv[])
{
 process();
 getchar();
 return 0;
}

 

 

    原文作者:天地一扁舟
    原文地址: https://blog.csdn.net/qingyuanluofeng/article/details/39273169
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞