c语言程序的健壮性_健壮的C ++:对象池

c语言程序的健壮性

介绍 (Introduction)

A system that needs to be continuously available must recover from memory leaks so that it doesn't need to be periodically shut down for "routine maintenance". This article describes how object pools help to meet this requirement. Here, object pool does not refer to a pool of shared objects that are never destroyed. Rather, it refers to objects whose memory is allocated from a pool of fixed-size blocks instead of the heap.

需要连续可用的系统必须从内存泄漏中恢复,因此不需要为“例行维护”而定期关闭它。 本文介绍了对象池如何帮助满足此要求。 在这里, 对象池 并非指的是永远不会被销毁共享对象池。 相反,它指的是从固定大小的块池而不是堆中分配内存的对象。

背景 (Background)

In C++, an object allocated from the heap, by the default implementation of operator new, must be explicitly deleted to return its memory to the heap. A memory leak results when deletion doesn't occur, usually because the pointer to the object gets lost. The addition of smart pointers in C++11 has reduced this risk, but it still exists (if a unique_ptr gets trampled, for example).

在C ++中,必须显式删除operator new的默认实现从堆分配的对象,以将其内存返回给堆。 当不进行删除时会导致内存泄漏,通常是因为指向对象的指针丢失了。 在C ++ 11中添加智能指针已降低了这种风险,但这种风险仍然存在(例如,如果unique_ptr被践踏)。

Another risk is memory fragmentation. Holes develop as memory blocks of differing sizes are allocated from, and returned to, the heap. Over a long period of time, this reduces the amount of effectively available memory unless the heap manager spends time merging adjacent free areas and implementing a best-fit policy.

另一个风险是内存碎片。 当从堆分配不同大小的内存块并将其返回到堆时,会出现漏洞。 在很长一段时间内,这将减少有效可用的内存量,除非堆管理器花时间合并相邻的空闲区域并实施最佳匹配策略。

The use of object pools allows a system to recover from memory leaks and avoid escalating fragmentation. As we shall see, it also enables some other useful capabilities.

使用对象池可以使系统从内存泄漏中恢复并避免碎片升级。 正如我们将看到的,它还启用了一些其他有用的功能。

使用代码 (Using the Code)

Unlike many techniques, it is often possible to introduce object pools to a large legacy system without the need for significant reengineering. The reason is that allocating and freeing an object's memory is encapsulated by operators new and delete. By tweaking the class hierarchy, the usual versions of these operators, which use the default heap, are easily replaced with versions that use an object pool.

与许多技术不同,通常可以将对象池引入大型遗留系统,而无需进行大量的重新设计。 原因是分配和释放对象的内存由运算符newdelete封装。 通过调整类层次结构,这些使用默认堆的运算符的常规版本可以轻松地替换为使用对象池的版本。

As in Robust C++: Safety Net, the code that we will look at is taken from the Robust Services Core (RSC), an open-source framework for building robust C++ applications. But this time, the code is more amenable to being copied into an existing project and modified to meet its needs.

就像在Robust C ++:Safety Net中一样 ,我们将要看的代码来自Robust Services Core (RSC),RSC是用于构建健壮C ++应用程序的开源框架。 但是这次,代码更适合复制到现有项目中并进行修改以满足其需求。

课程概述 (Overview of the Classes)

The classes are implemented in RSC's nb directory and follow the practice of being defined and implemented in a .h and .cpp of the same name. It should therefore be easy for you to find their full versions.

这些类在RSC的nb目录中实现,并遵循在同名.h.cpp中定义和实现的做法。 因此,您应该很容易找到它们的完整版本。

对象池 (ObjectPool)

ObjectPool is the base class for object pools, and each of its subclasses is a singleton that implements one pool. These subclasses do little other than invoke ObjectPool's constructor with the appropriate arguments. Each subclass, when created, gets added to an ObjectPoolRegistry that tracks all of the system's object pools. The blocks in each pool are allocated during system initialization so that the system can focus on its work once it is up and running.

ObjectPool是对象池的基类,其每个子类都是实现一个池的单例。 这些子类除了使用适当的参数调用ObjectPool的构造函数外,几乎没有其他作用。 每个子类在创建后都会添加到一个ObjectPoolRegistry中,该对象跟踪系统的所有对象池。 在系统初始化期间分配每个池中的块,以便一旦系统启动并运行,系统便可以专注于其工作。

汇集 (Pooled)

Pooled is the base class for objects whose memory comes from a pool. It overrides operator delete to return a block to its pool when an object is deleted. It also defines some data members that a pool uses to manage each block.

Pooled是其内存来自池的对象的基类。 delete对象时,它会覆盖运算符delete以将块返回其池中。 它还定义了池用来管理每个块的一些数据成员。

对象池审核 (ObjectPoolAudit)

ObjectPoolAudit is a thread which periodically wakes up to invoke a function that finds and returns orphaned blocks to the pool. Applications are still expected to delete objects, so the audit exists to fix memory leaks that could gradually cause the system to run out of memory. The audit uses a typical mark-and-sweep strategy but could be termed a background, rather than a foreground, garbage collector. It can run less frequently than a regular garbage collector, without freezing the system for an unbecoming length of time while performing its work.

ObjectPoolAudit是一个线程,该线程定期唤醒以调用一个函数,该函数查找孤立的块并将其返回到池中。 仍然期望应用程序delete对象,因此存在审核以修复可能逐渐导致系统内存不足的内存泄漏。 审核使用典型的标记清除策略,但可以称为后台垃圾收集器,而不是前台垃圾收集器。 与常规垃圾收集器相比,它的运行频率较低,而在执行工作时不会冻结系统一段时间。

      演练 (Walkthroughs)

      The code in this article has been edited to remove things that would distract from the central concepts. These things are important in some applications, less so in others. If you look at the full version of the code, you will run across them, so a summary of what was removed is provided in Deleted Code.

      已对本文中的代码进行了编辑,以删除可能分散中心概念的内容。 这些东西在某些应用程序中很重要,而在其他应用程序中则不那么重要。 如果您查看该代码的完整版本,将在它们上运行,因此在Deleted Code中提供了删除内容的摘要。

      创建一个对象池 (Creating an Object Pool)

      Each singleton subclass of ObjectPool invokes its base class constructor to create its pool:

      ObjectPool每个单例子类都调用其基类构造函数来创建其池:

      ObjectPool::ObjectPool(ObjectPoolId pid, size_t size, size_t segs) :
         blockSize_(0),
         segIncr_(0),
         segSize_(0),
         currSegments_(0),
         targSegments_(segs),
         corruptQHead_(false)
      {
         //  The block size must account for the header above each Pooled object.
         //
         blockSize_ = BlockHeaderSize + Memory::Align(size);
         segIncr_ = blockSize_ >> BYTES_PER_WORD_LOG2;
         segSize_ = segIncr_ * ObjectsPerSegment;
         for(auto i = 0; i < MaxSegments; ++i) blocks_[i] = nullptr;
      
         //  Initialize the pool's free queue of blocks.
         //  
         freeq_.Init(Pooled::LinkDiff());
      
         //  Set the pool's identifier and add it to the registry of object pools.
         //
         pid_.SetId(pid);
         Singleton< ObjectPoolRegistry >::Instance()->BindPool(*this);
      }
      
      

      Memory::Align aligns each block to the underlying platform's word size (32 or 64 bits). Similar to a heap, an object pool needs some data to manage its block. It puts a BlockHeader at the start of each block:

      Memory::Align每个块与基础平台的字长(32或64位) Memory::Align 。 与堆类似,对象池需要一些数据来管理其块。 它将BlockHeader放在每个块的开头:

      //  The header for a Pooled (a block in the pool).  Data in the header
      //  survives when an object is deleted.
      //
      struct BlockHeader
      {
         ObjectPoolId pid : 8;       // the pool to which the block belongs
         PooledObjectSeqNo seq : 8;  // the block's incarnation number
      };
      
      const size_t BlockHeader::Size = Memory::Align(sizeof(BlockHeader));
      
      //  This struct describes the top of an object block for a class that
      //  derives from Pooled.
      //
      struct ObjectBlock
      {
         BlockHeader header;  // block management information
         Pooled obj;          // the actual location of the object
      };
      
      constexpr size_t BlockHeaderSize = sizeof(ObjectBlock) - sizeof(Pooled);
      
      

      For reference, here are the members of Pooled that are relevant to this article:

      作为参考,以下是与本文相关的Pooled成员:

      //  A pooled object is allocated from an ObjectPool created during system
      //  initialization rather than from the heap.
      //
      class Pooled : public Object
      {
         friend class ObjectPool;
      public:
         //  Virtual to allow subclassing.
         //
         virtual ~Pooled() = default;
      
         //  Returns the offset to link_.
         //
         static ptrdiff_t LinkDiff();
      
         //  Overridden to claim blocks that this object owns.
         //
         void ClaimBlocks() override;
      
         //  Clears the object's orphaned_ field so that the object pool audit
         //  will not reclaim it.  May be overridden, but the base class version
         //  must be invoked.
         //
         void Claim() override;
      
         //  Overridden to return a block to its object pool.
         //
         static void operator delete(void* addr);
      
         //  Deleted to prohibit array allocation.
         //
         static void* operator new[](size_t size) = delete;
      protected:
         //  Protected because this class is virtual.
         //
         Pooled();
      private:
         //  Link for queueing the object.
         //
         Q1Link link_;
      
         //  True if allocated for an object; false if on free queue.
         //
         bool assigned_;
      
         //  Zero for a block that is in use.  Incremented each time through the
         //  audit; if it reaches a threshold, the block is deemed to be orphaned
         //  and is recovered.
         //
         uint8_t orphaned_;
      
         //  Used by audits to avoid invoking functions on a corrupt block.  The
         //  audit sets this flag before it invokes any function on the object.
         //  If the object's function traps, the flag is still set when the audit
         //  resumes execution, so it knows that the block is corrupt and simply
         //  recovers it instead of invoking its function again.  If the function
         //  returns successfully, the audit immediately clears the flag.
         //
         bool corrupt_;
      
         //  Used to avoid double logging.
         //
         bool logged_;
      };
      
      

      The constructor initialized a queue (freeq_) for blocks that have yet to be allocated. RSC provides two queue templates, Q1Way and Q2Way. Their implementation differs from STL queues in that they never allocate memory after the system initializes. Instead, the class whose objects will be queued provides a ptrdiff_t offset to a data member that serves as a link to the next item in the queue. That offset was the argument to freeq_.Init().

      构造函数为尚未分配的块初始化了一个队列( freeq_ )。 RSC提供了两个队列模板Q1WayQ2Way 。 它们的实现不同于STL队列,因为它们从不初始化系统初始化后分配的内存。 相反,其对象将排队的类为数据成员提供ptrdiff_t偏移量,该偏移量用作到队列中下一项的链接。 该偏移量是freeq_.Init()的参数。

      A key design decision is the number of pools. The recommended approach is to use a common pool for all classes that derive from the same major framework class. RSC's NodeBase, for example, defines an object pool for the buffers used for inter-thread messaging. Note that the size of a pool's blocks must be somewhat larger than, for example, sizeof(MsgBuffer) so that subclasses will have room to add data of their own.

      一个关键的设计决定是池的数量。 推荐的方法是对从同一主要框架类派生的所有类使用公共池。 例如,RSC的NodeBase为用于线程间消息传递的缓冲区定义了一个对象池。 请注意,池块的大小必须比sizeof(MsgBuffer)大一些,以便子类有空间添加自己的数据。

      Using a common pool for all classes that derive from the same framework class significantly simplifies the engineering of pool sizes. Each pool must have enough blocks to handle times of peak load, when the system is maxed out. If every subclass had its own pool, each pool would need enough blocks to handle times when that subclass just happened to be especially popular. Having the subclasses share a pool smooths out such fluctuations, reducing the total number of blocks required.

      对从同一框架类派生的所有类使用公用池,可以大大简化池大小的设计。 每个池必须有足够的块来处理系统达到最大负荷时的峰值负载时间。 如果每个子类都有自己的池,则每个池将需要足够的块来处理该子类恰好特别受欢迎的时间。 让子类共享一个池可以消除这种波动,从而减少了所需的块总数。

      增加对象池的大小 (Increasing the Size of an Object Pool)

      Whether a pool is creating its initial pool of blocks during system initialization, or whether it is allocating more blocks while in service, the code is the same:

      无论是池是在系统初始化期间创建其初始块池,还是在使用中分配更多的块,代码都是相同的:

      bool ObjectPool::AllocBlocks()
      {
         auto pid = Pid();
      
         while(currSegments_ < targSegments_)
         {
            //  Allocate memory for the next group of blocks.
            //
            auto size = sizeof(uword) * segSize_;
            blocks_[currSegments_] = (uword*) Memory::Alloc(size, false);
            if(blocks_[currSegments_] == nullptr) return false;
            ++currSegments_;
            totalCount_ = currSegments_ * ObjectsPerSegment;
      
            //  Initialize each block and add it to the free queue.
            //
            auto seg = blocks_[currSegments_ - 1];
      
            for(size_t j = 0; j < segSize_; j += segIncr_)
            {
               auto b = (ObjectBlock*) &seg[j];
               b->header.pid = pid;
               b->header.seq = 0;
               b->obj.link_.next = nullptr;
               b->obj.assigned_ = false;
               b->obj.orphaned_ = OrphanThreshold;
               EnqBlock(&b->obj);
            }
         }
      
         return true;
      }
      
      

      Note that blocks are allocated in segments of 1K blocks each, such that an individual block is addressed by blocks_[i][j]. This makes it easy to allocate more blocks when the system is running: just add another segment. The fact that blocks in a segment are contiguous is useful for other reasons that will appear later.

      注意,将块分配为每个1K块的段,以便使用blocks_[i][j]寻址单个块。 这使得在系统运行时轻松分配更多块:只需添加另一个段。 段中的块是连续的这一事实对于以后将要出现的其他原因很有用。

      创建一个池化对象 (Creating a Pooled Object)

      Let's turn our attention to creating an object that resides in an ObjectPool's block. This is done in the usual way, but the call to new ends up invoking an implementation in the framework class that uses the pool:

      让我们将注意力转向创建驻留在ObjectPool块中的对象。 这是通过通常的方式完成的,但是对new调用最终会在使用该池的框架类中调用一个实现:

      void* MsgBuffer::operator new(size_t size)
      {
         return Singleton< MsgBufferPool >::Instance()->DeqBlock(size);
      }
      
      

      Maybe you noticed this in Pooled, the base class for all pooled objects:

      也许您在Pooled注意到了所有池对象的基类中的这一点:

      static void* operator new[](size_t size) = delete;
      
      

      This prohibits the allocation of an array of pooled objects, which would have to be implemented by finding a set of adjacent free blocks and removing each one from wherever it happened to be on the free queue. Pass.

      这禁止分配池化对象的数组,这必须通过找到一组相邻的空闲块并将其从空闲队列中的任何位置删除的方法来实现。 通过。

      The code invoked by each base class's implementation of operator new looks like this:

      每个基类的operator new实现调用的代码如下:

      Pooled* ObjectPool::DeqBlock(size_t size)
      {
         //  Check that the object will fit into the block.
         //
         auto maxsize = blockSize_ - BlockHeader::Size;
      
         if(size > maxsize)
         {
            throw AllocationException(size);
         }
      
         auto item = freeq_.Deq();
      
         if(item == nullptr)
         {
            throw AllocationException(size);
         }
      
         --availCount_;
         return item;
      }
      
      

      When an object doesn't fit into its pool's block, the easiest solution is to increase the size of the blocks. A small increase should be tolerable, but if one class's objects are much larger than the other objects that use the pool, too much memory could be wasted. In that case, the offending class must use the PIMPL idiom to move some of its data into a private object. By default, this object will be allocated from the heap. However, it is also possible to provide auxiliary data blocks for this purpose. These also use object pools and come in various sizes, such as small, medium, and large. They are not difficult to implement, so RSC will eventually include them.

      当对象不适合其池的块时,最简单的解决方案是增加块的大小。 可以容忍少量增加,但是如果一个类的对象比使用该池的其他对象大得多,则可能浪费过多的内存。 在这种情况下,有问题的类必须使用PIMPL惯用法将其某些数据移到private对象中。 默认情况下,将从堆中分配此对象。 但是,也可以为此目的提供辅助数据块 。 这些还使用对象池,并且大小各异,例如小,中和大。 它们并不难实现,因此RSC最终将包括它们。

      删除池对象 (Deleting a Pooled Object)

      When delete is invoked on a pooled object, it eventually finds its way to this:

      当在池对象上调用delete ,它最终找到了解决方法:

      void Pooled::operator delete(void* addr)
      {
         auto obj = (Pooled*) addr;
         auto pid = ObjectPool::ObjPid(obj);
         auto pool = Singleton< ObjectPoolRegistry >::Instance()->Pool(pid);
         if(pool != nullptr) pool->EnqBlock(obj, true);
      }
      
      

      Here, ObjectPool::ObjPid obtains the object pool's identifier from BlockHeader.pid, which appeared earlier and is located immediately above obj. This allows the operator to return the block to the correct pool:

      在这里, ObjectPool::ObjPid获得来自对象池的标识BlockHeader.pid ,较早出现,并正上方obj 。 这使操作员可以将块返回到正确的池:

      void ObjectPool::EnqBlock(Pooled* obj)
      {
         if(obj == nullptr) return;
      
         //  If a block is already on the free queue or another queue, putting it
         //  on the free queue creates a mess.
         //
         if(!obj->assigned_)
         {
            if(obj->orphaned_ == 0) return;  // already on free queue
         }
         else if(obj->link_.next != nullptr) return;  // on some other queue
      
         //  Trample over some or all of the object.
         //
         auto nullify = ObjectPoolRegistry::NullifyObjectData();
         obj->Nullify(nullify ? blockSize_ - BlockHeader::Size : 0);
      
         //  Return the block to the pool.
         // 
         auto block = ObjToBlock(obj);
      
         if(block->header.seq == MaxSeqNo)
            block->header.seq = 1;
         else
            ++block->header.seq;
      
         obj->link_.next = nullptr;
         obj->assigned_ = false;
         obj->orphaned_ = 0;
         obj->corrupt_ = false;
         obj->logged_ = false;
      
         if(!freeq_.Enq(*obj)) return;
         ++availCount_;
      }
      
      

      Note that we trampled the object before returning it to the pool. The amount of trampling is determined by a configuration parameter that is read into nullify. This allows the entire block to be filled with something like 0xfd bytes during testing. In released software, time is saved by only trampling the top of the object. This is similar to the "debug" version of many heaps. The idea is that, by trampling the object, stale accesses (to deleted data) are more likely to be detected. Even if we only trample the top of the object, we corrupt its vptr, which will cause an exception if someone later tries to invoke one of its virtual functions.

      请注意,我们在将对象返回池之前先对其进行踩踏。 践踏的数量由配置参数确定,该参数被读取为nullify 。 这样可以在测试期间将整个块填充为0xfd字节。 在发布的软件中,仅踩踏对象的顶部即可节省时间。 这类似于许多堆的“调试”版本。 这个想法是,通过践踏对象,更有可能检测到陈旧的访问(对删除的数据)。 即使我们仅践踏对象的顶部,我们也会破坏其vptr ,如果以后有人尝试调用其virtual功能之一,则会导致异常。

      审核池 (Auditing the Pools)

      Earlier, we noted that ObjectPoolAudit uses a mark-and-sweep strategy to find orphaned blocks and return them to their pools. Its code is simple because ObjectPoolRegistry actually owns all of the object pools. The thread therefore just wakes up regularly and tells the registry to audit the pools:

      之前,我们注意到ObjectPoolAudit使用标记清除策略来查找孤立的块并将其返回到其池中。 它的代码很简单,因为ObjectPoolRegistry实际上拥有所有对象池。 因此,该线程只是定期唤醒,并告诉注册表审核池:

      void ObjectPoolAudit::Enter()
      {
         while(true)
         {
            Pause(interval_);
            Singleton< ObjectPoolRegistry >::Instance()->AuditPools();
         }
      }
      
      

      The audit has three distinct phases. The current phase_ and the identifier of the pool being audited (pid_) are members of ObjectPoolAudit itself but are used by this code:

      审核分为三个不同的阶段。 当前的phase_和正在审核的池的标识符( pid_ )是ObjectPoolAudit本身的成员,但是由以下代码使用:

      void ObjectPoolRegistry::AuditPools() const
      {
         auto thread = Singleton< ObjectPoolAudit >::Instance();
      
         //  This code is stateful.  When it is reentered after an exception, it
         //  resumes execution at the phase and pool where the exception occurred.
         //
         while(true)
         {
            switch(thread->phase_)
            {
            case ObjectPoolAudit::CheckingFreeq:
               //
               //  Audit each pool's free queue.
               //
               while(thread->pid_ <= ObjectPool::MaxId)
               {
                  auto pool = pools_.At(thread->pid_);
      
                  if(pool != nullptr)
                  {
                     pool->AuditFreeq();
                     ThisThread::Pause();
                  }
      
                  ++thread->pid_;
               }
      
               thread->phase_ = ObjectPoolAudit::ClaimingBlocks;
               thread->pid_ = NIL_ID;
               //  [[fallthrough]]
      
            case ObjectPoolAudit::ClaimingBlocks:
               //
               //  Claim in-use blocks in each pool.  Each ClaimBlocks function
               //  finds its blocks in an application-specific way.  The blocks
               //  must be claimed after *all* blocks, in *all* pools, have been
               //  marked, because some ClaimBlocks functions claim blocks from
               //  multiple pools.
               //
               while(thread->pid_ <= ObjectPool::MaxId)
               {
                  auto pool = pools_.At(thread->pid_);
      
                  if(pool != nullptr)
                  {
                     pool->ClaimBlocks();
                     ThisThread::Pause();
                  }
      
                  ++thread->pid_;
               }
      
               thread->phase_ = ObjectPoolAudit::RecoveringBlocks;
               thread->pid_ = NIL_ID;
               //  [[fallthrough]]
      
            case ObjectPoolAudit::RecoveringBlocks:
               //
               //  For each object pool, recover any block that is still marked.
               //  Such a block is an orphan that is neither on the free queue
               //  nor in use by an application.
               //
               while(thread->pid_ <= ObjectPool::MaxId)
               {
                  auto pool = pools_.At(thread->pid_);
      
                  if(pool != nullptr)
                  {
                     pool->RecoverBlocks();
                     ThisThread::Pause();
                  }
      
                  ++thread->pid_;
               }
      
               thread->phase_ = ObjectPoolAudit::CheckingFreeq;
               thread->pid_ = NIL_ID;
               return;
            }
         }
      }
      
      

      审核可用块的队列 (Auditing the Queue of Available Blocks)

      In its first phase, AuditPools invoked each pool's AuditFreeq function. This function begins by marking all of the pool's blocks as orphaned, after which it claims the blocks that are already on the free queue.

      在其第一阶段, AuditPools调用了每个池的AuditFreeq函数。 此功能首先将池中的所有块标记为孤立块,然后声明已在空闲队列中的块。

      A corrupt queue is likely to cause continuous exceptions. It is therefore up to AuditFreeq to detect if the free queue is corrupt and, if so, repair it:

      队列损坏可能会导致连续异常。 因此,由AuditFreeq来检测空闲队列是否损坏,如果损坏,则对其进行修复

      void ObjectPool::AuditFreeq()
      {
         size_t count = 0;
      
         //  Increment all orphan counts.
         //
         for(size_t i = 0; i < currSegments_; ++i)
         {
            auto seg = blocks_[i];
      
            for(size_t j = 0; j < segSize_; j += segIncr_)
            {
               auto b = (ObjectBlock*) &seg[j];
               ++b->obj.orphaned_;
            }
         }
      
         //  Audit the free queue unless it is empty.  The audit checks that
         //  the links are sane and that the count of free blocks is correct.
         //
         auto diff = Pooled::LinkDiff();
         auto item = freeq_.tail_.next;
      
         if(item != nullptr)
         {
            //  Audit the queue header (when PREV == nullptr), then the queue.
            //  The queue header references the tail element, so the tail is
            //  the first block whose link is audited (when CURR == freeq_).
            //  The next block to be audited (when PREV == freeq_) is the head
            //  element, which follows the tail.  The entire queue has been
            //  traversed when CURR == freeq_ (the tail) for the second time.
            //
            //  Before a link (CURR) is followed, the item (queue header or
            //  block) that provided the link is marked as corrupt.  If the
            //  link is bad, a trap should occur at curr->orphaned_ = 0.
            //  Thus, if we get past that point in the code, the link should
            //  be sane, and so its owner's "corrupt" flag is cleared before
            //  continuing down the queue.
            //
            //  If a trap occurs, this code is reentered.  It starts traversing
            //  the queue again.  Eventually it reaches an item whose corrupt_
            //  flag *is already set*, at which point the queue gets truncated.
            //
            Pooled* prev = nullptr;
            auto badLink = false;
      
            while(count <= totalCount_)
            {
               auto curr = (Pooled*) getptr1(item, diff);
      
               if(prev == nullptr)
               {
                  if(corruptQHead_)
                     badLink = true;
                  else
                     corruptQHead_ = true;
               }
               else
               {
                  if(prev->corrupt_)
                     badLink = true;
                  else
                     prev->corrupt_ = true;
               }
      
               //  CURR has not been claimed, so it should still be marked as
               //  orphaned (a value in the range 1 to OrphanThreshold).  If it
               //  isn't, PREV's link must be corrupt.  PREV might be pointing
               //  back into the middle of the queue, or it might be a random
               //  but legal address.
               //
               badLink = badLink ||
                  ((curr->orphaned_ == 0) || (curr->orphaned_ > OrphanThreshold));
      
               //  If a bad link was detected, truncate the queue.
               //
               if(badLink)
               {
                  if(prev == nullptr)
                  {
                     corruptQHead_ = false;
                     freeq_.Init(Pooled::LinkDiff());
                     availCount_ = 0;
                  }
                  else
                  {
                     prev->corrupt_ = false;
                     prev->link_.next = freeq_.tail_.next;  // tail now after PREV
                     availCount_ = count;
                  }
      
                  return;
               }
      
               curr->orphaned_ = 0;
               ++count;
      
               if(prev == nullptr)
                  corruptQHead_ = false;
               else
                  prev->corrupt_ = false;
      
               prev = curr;
               item = item->next;
      
               if(freeq_.tail_.next == item) break;  // reached tail again
            }
         }
      
         availCount_ = count;
      }
      
      

      声明使用中的块 (Claiming In-Use Blocks)

      In its second phase, AuditPools invoked each pool's ClaimBlocks function. It is this function that must claim in-use blocks so that the audit will not recover them. A pool must therefore be able to find all of the objects that could own an object from the pool, so that each owner can claim its objects.

      在第二阶段, AuditPools调用了每个池的ClaimBlocks函数。 此功能必须声明使用中的块,以便审核不会恢复它们。 因此,池必须能够从池中找到所有可能拥有对象的对象,以便每个所有者可以声明其对象。

      The process of claiming objects is a cascade through the system's object model. Earlier, we noted that NodeBase had a pool for the buffers used for inter-thread messaging. The following code fragments illustrate how in-use buffers are claimed.

      声明对象的过程是整个系统对象模型的级联。 之前,我们注意到NodeBase有一个用于线程间消息传递的缓冲区池。 以下代码片段说明了如何声明使用中的缓冲区。

      Threads own the buffers used for inter-thread messaging, so the buffer's pool implements ClaimBlocks by telling each thread to claim its objects. To do this, it goes through ThreadRegistry, which tracks all of the system's threads. This initiates the cascade:

      线程拥有用于线程间消息传递的缓冲区,因此缓冲区的池通过告诉每个线程声明其对象来实现ClaimBlocks 。 为此,它通过ThreadRegistry进行跟踪,该线程跟踪系统的所有线程。 这将启动级联:

      void MsgBufferPool::ClaimBlocks()
      {
         Singleton< ThreadRegistry >::Instance()->ClaimBlocks();
      }
      
      
      void ThreadRegistry::ClaimBlocks()
      {
         //  Have all threads mark themselves and their objects as being in use.
         //
         for(auto t = threads_.First(); t != nullptr; threads_.Next(t))
         {
            t->ClaimBlocks();
         }
      }
      
      

      Invoking ClaimBlocks on a thread soon reaches the following, in which the thread claims any message buffers queued against it:

      在线程上调用ClaimBlocks很快会达到以下条件,其中该线程声明对它排队的任何消息缓冲区:

      void Thread::Claim()
      {
         for(auto m = msgq_.First(); m != nullptr; msgq_.Next(m))
         {
            m->Claim();
         }
      }
      
      void Pooled::Claim()
      {
         orphaned_ = 0;  // finally!
      }
      
      

      恢复孤立块 (Recovering Orphaned Blocks)

      In its third and final phase, AuditPools invokes each pool's RecoverBlocks function, whcih recovers orphans. To guard against unforeseen race conditions, a block must remain orphaned for more than one audit cycle before it is reclaimed. This is the purpose of the constant OrphanThreshold, whose value is 2.

      在第三个也是最后一个阶段, AuditPools调用每个池的RecoverBlocks函数,以恢复孤儿。 为了防止不可预见的种族状况,必须在回收之前将一个块保持孤立状态超过一个审核周期。 这是常量OrphanThreshold的目的,该值是2

      void ObjectPool::RecoverBlocks()
      {
         auto pid = Pid();
      
         //  Run through all of the blocks, recovering orphans.
         //
         for(size_t i = 0; i < currSegments_; ++i)
         {
            auto seg = blocks_[i];
      
            for(size_t j = 0; j < segSize_; j += segIncr_)
            {
               auto b = (ObjectBlock*) &seg[j];
               auto p = &b->obj;
      
               if(p->orphaned_ >= OrphanThreshold)
               {
                  //  Generate a log if the block is in use (don't bother with
                  //  free queue orphans) and it hasn't been logged yet (which
                  //  can happen if we reenter this code after a trap).
                  //
                  ++count;
      
                  if(p->assigned_ && !p->logged_)
                  {
                     auto log = Log::Create(ObjPoolLogGroup, ObjPoolBlockRecovered);
      
                     if(log != nullptr)
                     {
                        *log << Log::Tab << "pool=" << int(pid) << CRLF;
                        p->logged_ = true;
                        p->Display(*log, Log::Tab, VerboseOpt);
                        Log::Submit(log);
                     }
                  }
      
                  //  When an in-use orphan is found, we mark it corrupt and clean
                  //  it up.  If it is so corrupt that it causes an exception during
                  //  cleanup, this code is reentered and encounters the block again.
                  //  It will then already be marked as corrupt, in which case it
                  //  will simply be returned to the free queue.
                  //
                  if(p->assigned_ && !p->corrupt_)
                  {
                     p->corrupt_ = true;
                     p->Cleanup();
                  }
      
                  b->header.pid = pid;
                  p->link_.next = nullptr;
                  EnqBlock(p);
               }
            }
         }
      }
      
      

          兴趣点 (Points of Interest)

          Now that we have seen how to implement an object pool that can repair a corrupt free queue and recover leaked objects, a few other capabilities of object pools should be mentioned.

          既然我们已经了解了如何实现可以修复损坏的空闲队列并恢复泄漏的对象的对象池,那么应该提到对象池的其他一些功能。

          遍历池对象 (Iterating Over Pooled Objects)

          ObjectPool provides iteration functions for this purpose. In the following code fragment, FrameworkClass is the class whose subclasses reside in pool's blocks:

          为此, ObjectPool提供了迭代功能。 在以下代码片段中, FrameworkClass是其子类位于pool的块中的类:

          PooledObjectId id;
          
          for(auto obj = pool->FirstUsed(id); obj != nullptr; obj = pool->NextUsed(id))
          {
             auto item = static_cast< FrameworkClass* >(obj);
             //  ...
          }
          
          

          验证指向池对象的指针 (Validating a Pointer to a Pooled Object)

          ObjectPool provides a function that takes a pointer to a pooled object and returns the object's identifier within the pool. This identifier is an integer between 1 and the number of blocks in the pool. If the pointer is invalid, the function returns NIL_ID:

          ObjectPool提供了一个函数,该函数获取指向池化对象的指针并返回池中对象的标识符。 该标识符是1到池中块数之间的整数。 如果指针无效,则函数返回NIL_ID

          PooledObjectId ObjectPool::ObjBid(const Pooled* obj, bool inUseOnly) const
          {
             if(obj == nullptr) return NIL_ID;
             if(inUseOnly && !obj->assigned_) return NIL_ID;
          
             //  Find BLOCK, which houses OBJ and is the address that we'll look for.
             //  Search through each segment of blocks.  If BLOCK is within MAXDIFF
             //  distance of the first block in a segment, it should belong to that
             //  segment, as long as it actually references a block boundary.
             //
             auto block = (const_ptr_t) ObjToBlock(obj);
             auto maxdiff = (ptrdiff_t) (blockSize_ * (ObjectsPerSegment - 1));
          
             for(size_t i = 0; i < currSegments_; ++i)
             {
                auto b0 = (const_ptr_t) &blocks_[i][0];
          
                if(block >= b0)
                {
                   ptrdiff_t diff = block - b0;
          
                   if(diff <= maxdiff)
                   {
                      if(diff % blockSize_ == 0)
                      {
                         auto j = diff / blockSize_;
                         return (i << ObjectsPerSegmentLog2) + j + 1;
                      }
                   }
                }
             }
          
             return NIL_ID;
          }
          
          

          区分方块的化身 (Distinguishing a Block's Incarnations)

          BlockHeader was introduced in Creating an Object Pool. EnqBlock incremented its seq member, which acts as an incarnation number. This is useful in distributed systems. Say that a pooled object receives messages from another processor and that it gives that processor its this pointer. That processor includes the pointer in each message that is to be delivered to the object. The following can then occur:

          BlockHeader创建对象池中引入。 EnqBlock增加了其seq成员,该成员用作化身数。 这在分布式系统中很有用。 假设一个池化对象从另一个处理器接收消息,并将this指针赋予该处理器。 该处理器将指针包含在要传递给对象的每个消息中。 然后会发生以下情况:

          1. The object is deleted and its block is quickly assigned to a new object.

            该对象将被删除,并且其块将快速分配给新对象。
          2. Before the other processor learns of the deletion, it sends the object a message.

            在另一个处理器获悉删除之前,它会向对象发送一条消息。
          3. The message arrives and gets delivered to the new object.

            消息到达并传递到新对象。

          Instead of providing this as its message address, the object should provide its PooledObjectId (as returned by the above function) and its incarnation number. That way, it is easy to detect that a message is stale and discard it. Note that if the object is not pooled, some other mechanism is needed to detect stale messages.

          代替提供的this作为其消息地址,对象应该提供其PooledObjectId 其化身数 (如由上述函数返回) 。 这样,很容易检测到邮件已过时并丢弃它。 请注意,如果合并对象,则需要其他某种机制来检测过期消息。

          An object obtains its PooledObjectId from ObjectPool::ObjBid, above. The reverse mapping is provided by ObjectPool::BidToObj, and ObjectPool::ObjSeq allows the object to obtain its incarnation number.

          对象从上面的ObjectPool::ObjBid获取其PooledObjectId 。 反向映射由ObjectPool::BidToObj ,而ObjectPool::ObjSeq允许对象获取其化身编号。

          删除的代码 (Deleted Code)

          The full version of the software includes aspects1 that were removed for the purposes of this article:

          该软件的完整版包括为本文目的而删除的方面1

          • Configuration parameters. Each pool's size is set by a configuration parameter whose value is read from a file when the system initializes. This allows the size of each pool to be customized, which is important in a server. When the system is running, a pool's size can be increased if it was under-engineered, simply by using a CLI command to change the value of this configuration parameter.

            配置参数 。 每个池的大小由一个配置参数设置,该参数在系统初始化时从文件中读取。 这允许自定义每个池的大小,这在服务器中很重要。 当系统运行时,如果池的设计欠佳,则可以通过使用CLI命令更改此配置参数的值来增加池的大小。

          • Logs. The code generated a log when recovering an orphaned block, but there are many other situations that also result in logs.

            日志 。 恢复孤立块时,该代码生成了一个日志,但是还有许多其他情况也会导致生成日志。

          • Statistics. Each pool provides statistics such as the number of times a block was allocated from the pool and the low watermark for the number of available blocks remaining in the pool.

            统计资料 。 每个池提供统计信息,例如从池中分配块的次数以及池中剩余的可用块数的低水位线。

          • Alarms. A pool raises an alarm when the number of available blocks drops below a threshold. The alarm's severity (minor, major, or critical) is determined by the number of available blocks (less than 1/8th, 1/16th, or 1/32nd of the total blocks in the pool). The alarm acts as a warning that the pool's size may need to be increased. The system can survive a moderate number of allocation failures, but it is unlikely to survive a flood of them.

            警报 。 当可用块数降至阈值以下时,池将发出警报。 警报的严重性(轻微,重要或关键的)由可用块的数目来确定(小于1/8,1/16,1/32池中的总块)。 该警报作为警告,可能需要增加池的大小。 该系统可以承受中等数量的分配失败,但不太可能幸免其中的大量失败。

          • Memory types. RSC defines memory types based on whether the memory will be write-protected and what level of restart it will survive. (A restart is a reinitialization that is less severe than a reboot.)  When it invokes ObjectPool's constructor, a subclass specifies the type of memory to use for the pool's blocks.

            内存类型 。 RSC根据是否对内存进行写保护以及在何种级别的重启中可以生存来定义内存类型。 (重新启动是一种重新初始化,其ObjectPool不如重新启动。)在调用ObjectPool的构造函数时,子类指定用于池块的内存类型。

          • Trace tools. Most functions invoke Debug::ft in their first statement. It supports a function trace tool and other things mentioned in Robust C++ : Safety Net. There is also a trace tool that records object blocks being allocated, claimed, and freed.

            跟踪工具 。 大多数函数在其第一条语句中调用Debug::ft 。 它支持功能跟踪工具以及Robust C ++:Safety Net中提到的其他功能。 还有一个跟踪工具,记录被分配,声明和释放的对象块。

          笔记 (Notes)

          1 Most of these would indeed be aspects in aspect-oriented programming.

          1其中大多数确实是面向方面编程中的方面。

          翻译自: https://www.codeproject.com/Articles/5166096/Robust-Cplusplus-Object-Pools

          c语言程序的健壮性

          • 0
            点赞
          • 0
            收藏
            觉得还不错? 一键收藏
          • 0
            评论
          矩阵运算的健壮性设计是指在进行矩阵运算时,能够处理非法输入数据或异常情况,确保算法的正确性和稳定性。以下是C语言中矩阵运算健壮性设计的一些建议: 1. 输入数据合法性检查:在进行矩阵运算之前,应该检查输入的矩阵是否满足运算的要求,例如矩阵的行列数是否匹配,矩阵是否为空等。如果输入的矩阵不满足要求,可以给出相应的错误提示或返回错误码。 2. 内存分配检查:在进行矩阵运算时,需要为结果矩阵分配内存空间。在分配内存之前,应该检查内存分配是否成功,如果分配失败,应该给出相应的错误提示或返回错误码。 3. 数值溢出检查:在进行矩阵运算时,可能会出现数值溢出的情况。例如,两个矩阵相乘时,结果可能会超出数据类型的表示范围。为了避免数值溢出,可以在运算之前检查运算结果是否超出了数据类型的表示范围,如果超出了,可以给出相应的错误提示或返回错误码。 4. 异常情况处理:在进行矩阵运算时,可能会出现一些异常情况,例如除数为零、矩阵不存在逆矩阵等。为了处理这些异常情况,可以使用条件语句或异常处理机制来捕获并处理异常,以保证程序的正常运行。 5. 错误处理和恢复:在进行矩阵运算时,如果发生错误,应该及时处理错误并进行相应的恢复操作。例如,释放已分配的内存空间、关闭打开的文件等。 下面是一个示例代码,演示了C语言中矩阵相加的健壮性设计: ```c #include <stdio.h> #define MAX_SIZE 100 void matrixAdd(int matrix1[][MAX_SIZE], int matrix2[][MAX_SIZE], int result[][MAX_SIZE], int rows, int cols) { if (rows <= 0 || cols <= 0) { printf("Invalid matrix size.\n"); return; } for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { result[i][j] = matrix1[i][j] + matrix2[i][j]; } } } int main() { int matrix1[MAX_SIZE][MAX_SIZE]; int matrix2[MAX_SIZE][MAX_SIZE]; int result[MAX_SIZE][MAX_SIZE]; int rows, cols; printf("Enter the number of rows and columns: "); scanf("%d %d", &rows, &cols); if (rows <= 0 || cols <= 0) { printf("Invalid matrix size.\n"); return 0; } printf("Enter the elements of matrix1:\n"); for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { scanf("%d", &matrix1[i][j]); } } printf("Enter the elements of matrix2:\n"); for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { scanf("%d", &matrix2[i][j]); } } matrixAdd(matrix1, matrix2, result, rows, cols); printf("The result matrix is:\n"); for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { printf("%d ", result[i][j]); } printf("\n"); } return 0; } ```

          “相关推荐”对你有帮助么?

          • 非常没帮助
          • 没帮助
          • 一般
          • 有帮助
          • 非常有帮助
          提交
          评论
          添加红包

          请填写红包祝福语或标题

          红包个数最小为10个

          红包金额最低5元

          当前余额3.43前往充值 >
          需支付:10.00
          成就一亿技术人!
          领取后你会自动成为博主和红包主的粉丝 规则
          hope_wisdom
          发出的红包
          实付
          使用余额支付
          点击重新获取
          扫码支付
          钱包余额 0

          抵扣说明:

          1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
          2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

          余额充值