返回介绍

单线程备份 (下)

发布于 2024-10-04 12:28:35 字数 25102 浏览 0 评论 0 收藏 0

0x0E-单线程备份(下)

写在最前方

  • 按部就班的完成一件事情,是十分美妙的感觉。
  • 在这里并没有使用Makefile系列的构造工具,而是使用集成开发环境直接一站式的完成所有的工作,而我们只需要专注于编写有用的代码即可。
  • 但是对于这些构造工具的功能还是需要了解的,到了性能瓶颈期,往往是需要这些东西的微调来进行提升,就像算法为什么有那么多的排序算法,看上去复杂度都是一样的,但是快速排序却往往比堆排序要快?不就是因为局部性快速排序要优于堆排序吗?换句话说就是缓存的命中率高
  • 不了解底层,永远也无法理解这个解释,但是前方已经有提到过什么叫做空间局部性时间局部性,至少能有些理解了。
  • 构造工具也是如此,例如,编译了源文件生成了库文件,当我们在某个函数中通过该库调用这个库中的某些函数,这个库会在在一开始就加载进我们的程序。当我们的程序十分庞大的时候,也许我们希望在使用的时候才使用它,那么就需要延迟加载这个编译器技术。如果没有了解过构造工具,这些根本不会懂,并且某些情况下UnixLinuxWindows对于库的加载方式是不同的,这些都是需要了解的,但是我们现在的确没有必要,这个程序满打满算也就是四五百行的代码,不太需要考虑这些。

写在中间

  • 上回完成了界面的大部分功能,剩下的便是备份这个主要功能。
  • 在完成备份之前,首先想想要如何构造这个备份模型

    • 既然是备份,如果不想扩展为多线程的形式,参考第一次写的遍历函数(show_structure)直接找到文件便调用Windows API(稍后介绍)进行复制即可,不需要讲待备份的文件路径保存下来。
    • 如果要考虑多线程扩展,我们就需要从长计议。
    • 对于一个备份模型,最好的莫过于使用一个队列,依旧实行的是遍历模式,但是将找到的文件路径保存,并放入一个先进先出队列中,这样我们就能够保证在扩展成多线程的时候,可以有一个很清晰的模型参考。
    • 那么现在的任务就是实现这个用于备份的队列模型。
  • 队列模型

    • 应该有一个容器空间:用于存放路径
    • 有队首队尾标志
    • O(1)复杂度的检查队列是否为空的接口或标志
    • O(1)复杂度的返回容器容量的接口或标志,容器容量应该固定不变
    • 使用一些面向对象的黑魔法,保存一些操作函数防止代码混乱。
      • 初始化函数
      • 释放函数
      • 弹出操作函数
      • 压入操作函数
  • 队列实体

    • 考虑到要存储的是字符串,并且由于Windows API的参数需求,对于一个文件,我们需要存储的路径有两个<源路径,目的路径>,对此应该再使用一个路径模型结构体包裹他们,则空间的类型就相应改变一下
  • 新建 Queue.h Queue.c

    • Queue.h

        typedef struct _vector_queue queue;
        typedef struct _combine combine;
      
                |    返回值    | | 函数类型名 ||   参数类型   |
        typedef int             (*fpPushBack)(queue * __restrict, const char * __restrict, const char * __restrict);
        typedef const combine * (*fpPopFront)(queue *);
        typedef void            (*fpDelete)(queue *);
      

      五个typedef不知道有没有眼前一懵。,希望能够很好的理解

      前两个是结构体的声明,分别对应着 队列模型 和 路径模型。

      后两个是函数指针,作用是放在结构体里,使C语言的结构体也能够拥有一些简单的面向对象功能,例如成员函数功能,原理就是可以给这些函数指针类型的变量赋值。稍后例子更加明显。试着解读一下,很简单的。

        struct _combine{
        char * src_from_path; /* 源路径 */
        char * dst_to_path;   /* 目的路径 */
        };

        struct _vector_queue{
            combine **      path_contain; /* 存储路径的容器主体 */
            unsigned int    rear;         /* 队尾坐标 */
            unsigned int    front;        /* 队首坐标 */
            int             empty;        /* 是否为空 */
            unsigned int    capcity;      /* 容器的容量 */
            fpPushBack      PushBack;  /* 将元素压入队尾 */
            fpPopFront      PopFront;  /* 将队首出队 */
            fpDelete        Delete;    /* 析构释放整个队列空间 */
        };

        /**
         * @version  1.0 2015/10/03
         * @author   wushengxin
         * @param    object 外部传入的对象指针,相当于 this
         * @function 初始化队列模型,建立队列实体,分配空间,以及设置属性。
         */
        int newQueue(queue* object);

可以看到,上方的函数指针类型,被用在了结构体内,此处少了一个**初始化函数**,是因为不打算把他当作**成员函数(借用面向对象术语)**

在使用的时候可以直接`obj_name.PushBack(..., ..., ...);`

更详细的可以看后面的实现部分。成为成员函数的三个函数,将被实现为 `static` 函数,不被外界访问。
  • queue.c

      int newQueue(queue * object)
      {
          queue*      loc_que = object;
          combine**   loc_arr = NULL;
    
          loc_arr = (combine**)Malloc_s(CAPCITY * sizeof(combine*));
          if (!loc_arr)
              return 1;
    
          loc_que->capcity = CAPCITY; /* 容量 */
          loc_que->front = 0;        /* 队首 */
          loc_que->rear = 0;        /* 队尾 */
    
          loc_que->path_contain = loc_arr; /* 将分配好的空间,放进对象中 */
          loc_que->PushBack = push_back;
          loc_que->PopFront = pop_front;
          loc_que->Delete   = del_queue;
    
          return 0;
      }
    

    在初始化函数中,可以看到,设置了队首队尾以及容量,分配了容器空间,配置了成员函数。

    最后三句配置函数的语句中,push_back, pop_front, del_queue在后方以static 函数实现。

    但是由于没有声明,所以切记要将三个static函数的实现放在newQueue的前方

      /**
       * @version  1.0 2015/10/03
       * @author   wushengxin 
       * @param    object 外部传入的对象指针 相当于 this
       * @function 释放整个队列实体的空间
       */
      static void del_queue(queue * object)
      {
          Free_s(object->path_contain);
          return;
      }
    
      /**
       * @version  1.0 2015/10/03
       * @author   wushengxin
       * @param    object 外部传入的对象指针 相当于 this
                   src    源路径
                   dst    目的路径
       * @function 将外部传入的<源路径,目的路径> 存入队列中
       */
      static int push_back(queue * __restrict object, const char * __restrict src, const char * __restrict dst)
      {
          int times = 0;
          char*    loc_src = NULL; /* 本地变量,尽量利用寄存器以及缓存 */
          char*    loc_dst = NULL;
          combine* loc_com = NULL;
          queue*   loc_que = object;
    
          size_t   len_src = strlen(src); /* 获取路径长度 */
          size_t   len_dst = strlen(dst);
          size_t   rear = loc_que->rear;   /*获取队尾*/
          size_t   front = loc_que->front; /*获取队首*/
    
          loc_src = Malloc_s(len_src + 1); /* 分配空间 */
          if (!loc_src)
              return 1;
    
          loc_dst = Malloc_s(len_dst + 1);
          if (!loc_dst)
              return 2;
          strcpy(loc_src, src);
          strcpy(loc_dst, dst);
    
          loc_com = Malloc_s(sizeof(combine));
          if (!loc_com)
              return 3;
          loc_com->dst_to_path = loc_dst; 
          loc_com->src_from_path = loc_src;
    
          loc_que->path_contain[rear++] = loc_com; /* 将本地路径加入实体 */
          loc_que->rear = (rear % CAPCITY);     /* 用数组实现循环队列的步骤 */
    
          if (loc_que->rear == loc_que->front)  
              loc_que->empty = 0;
          return 0;
      }
    
      /**
       * @version  1.0 2015/10/03
       * @author   wushengxin
       * @param    object 外部传入的对象指针
       */
      static const combine * pop_front(queue* object)
      {
          size_t   loc_front = object->front;                   /*获取当前队首*/
          combine* loc_com   = object->path_contain[loc_front]; /*获取当前文件名*/
          object->path_contain[loc_front] = NULL;     /*出队操作*/
          object->front = ((object->front) + 1) % 20; /*完成出队*/
    
          if (object->front == object->rear)
              object->empty = 1;
          else
              object->empty = 0;
          return loc_com;
      }
    

    一个一个的说这些函数

    del_queue:释放函数,直接调用Free_s

    push_back:压入函数,将外部传入的两个原始的没有组成的路径字符串,组合成一个combine,并压入路径,每次都判断并置是否为空标志位,实际上这个函数中有累赘代码的嫌疑,应该再分出一个函数,专门用来分配三个空间,防止这个函数过长(接近40行)

    pop_front:弹出函数,将队列的队首combine弹出,用于复制,但是这里有一个隐患,就是要将释放的工作交给外者,如果疏忽大意的话,隐患就是内存泄漏

    没有特地的提供一个接口,用来判断是否为空,因为当编译器一优化,也会将这种接口给优化成直接使用成员的形式,某种形式上的内联

  • 队列模型设计完毕,可以开始设计备份模型

  • 备份模型可以回想一下之前的遍历函数,大体的结构一样,只是此处为了扩展成多线程,需要添加一些多线程的调用函数,以及为了规格化,需要添加一个二级界面
  • 先设计一下二级界面

  • 二级界面

    • 思考一下,这个界面要做什么
      • 选择是否开始备份
      • 并且源路径需要在此处输入
      • 返回上一级
    • 新建 backup.h backup.c 文件
      • 在主界面选择 1 以后就会调用二级界面的函数
      • 列出二级界面的选项
        • 1 Start Back up
        • 2 Back To last level
    • backup.h

        /**
         * @version  1.0 2015/10/03
         * @author   wushengxin
         * function  显示二级界面
         */
        void sec_main_windows();
      
    • backup.c

        void sec_main_windows()
        {
            char tmpBuf[256];
            int selects;
            do{
                setjmp(select_jmp);
                system("cls");
                puts("-------------------1. Back Up------------------ ");
                puts(" For This Select, You can choose Two Options: ");
                puts(" 1. Start Back up (The Directory Path That You Enter LATER) ");
                puts(" 2. Back To last level ");
                puts("----------------------------------------------- ");
                fprintf(stdout, "Enter Your Selection: ");
                fgets(tmpBuf, 256, stdin);
                sscanf(tmpBuf, "%d", &selects);
                if (selects != 1 && selects != 2 )
                {
                    fprintf(stdout, "\n Your Select \" %s \" is Invalid!\n Try Again \n", tmpBuf);
                    longjmp(select_jmp, 1);
                }
      
                switch (selects)
                {
                    jmp_buf enter_path_jmp; 
                case 1:
                {
                    char tmpBuf[LARGEST_PATH], tmpPath[LARGEST_PATH]; /* 使用栈分配空间,因为只用分配一次 */
                    setjmp(enter_path_jmp);         /* enter jump to there */
                    puts(" Enter the Full Path You want to BackUp(e.g: C:/Programing/)");
                    fprintf(stdout, " Or Enter q to back to select\nYour Enter : ");
                    fgets(tmpBuf, LARGEST_PATH, stdin);
                    sscanf(tmpBuf, "%s", tmpPath);
                    if (_access(tmpPath, 0) != 0)   /*检查路径是否存在,有效*/
                    {
                        if (tmpPath[0] == 'q' || tmpPath[0] == 'Q') 
                            longjmp(select_jmp, 0); /* 回到可以选择返回的界面 */
                        fprintf(stderr, "The Path You Enter is Not Exit! \n Try Again : ");
                        longjmp(enter_path_jmp, 0); /* enter jump from here */
                    }
                }
                    break;
                case 2:
                    return;
                default:
                    break;
                }/* switch */
            } while (1);
            return;
        }
      

      这个函数只说几点,首先是switchcase 1,之所以用花括号包裹起来的原因是,这样才能在里面定义本地变量,直接在冒号后面定义是编译错误,这个特性可能比较少用,这里提一下,前面也有说过。

写在最后方

  • 剩下的就是编写主要的功能函数和线程调用函数了。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文