如何写优雅的代码(5)——远离临界区噩梦

【Window】线程同步方式1——临界区(关键代码段) 文章目录1 临界区1.1 简介1.2 程序调度法则1.3 线程同步问题2 临界区操作原语2.1 定义全局的锁CRITICAL_SECTION2)InitializeCriticalSection3)EnterCriticalSection和LeaveCriticalSection4) DeleteCriticalSection3 应用和举例 1 临界区 1.1 简介 临界区,也称为关键代码段,工作在用户方式下。它是指一个小代码段,在代码能够执行前,它必须独占对某些资源的访问权。 临界区(Critical S 阅读详情

分享一下我老师大神的人工智能教程。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.youkuaiyun.com/jiangjunshow

               

//========================================================================
//TITLE:
//    如何写优雅的代码(5)--远离临界区噩梦
//AUTHOR:
//    norains
//DATE:
//    Tuesday 01- December-2009
//Environment:
//    WINDOWS CE 5.0
//========================================================================

  在平时的多线程编程中,我们往往离不开临界区。会用,甚至是用得好的朋友,估计不少;但用得万无一失的,相对而言,可能就会少很多。
  
  不信?我们来看看下面这段代码:  
  try  {    EnterCriticalSection(&g_cs);    Cal3rdPartCode(pParam);    LeaveCriticalSection(&g_cs);  }  catch(...)  {    //There is error    //dwRet = 0;  }

 

  这段代码看起来似乎没有什么问题?但如果说,这段代码会引发孤立临界区的问题,你会信么?换句话说,如果Cal3rdPartCode(pParam)函数会抛出异常,那么代码就直接跳转到catch部分。那事情就严重了,因为代码根本就没有调用LeaveCriticalSection(&g_cs),将g_cs这临界区完全孤立了起来,别的线程就只能一直等待该临界区。
  
  似乎情况很糟糕,不是么?当然,糟糕并不代表陷入世界末日,虽然路途上充满了荆棘,但毕竟我们还是能看到阳光。
  
  我们总不能在一棵树上吊死吧。换个角度,临界区的使用,必须是EnterCriticalSection和LeaveCriticalSection配套使用。那么我们想想,C++里面,有什么东西是强制配套的,根本不用使用者干预的?也许有的朋友已经想到了。没错,是类,类的构造函数和析构函数。
  
  正常使用的话,无论使用者愿不愿意,类的构造函数和析构函数都是被编译器强制配套使用。所以,我们可以在构造函数中调用EnterCriticalSection,在析构函数则是LeaveCriticalSection。
  
  根据此思想,我们最简单的用类来调用临界区的代码诞生了:
       class CLock     {       public:         CLock(LPCRITICAL_SECTION pCriticalSection):         m_ pCriticalSection(pCriticalSection)    {      EnterCriticalSection(m_ pCriticalSection);    };        virtual ~CLock()    {      LeaveCriticalSection (m_ pCriticalSection);    };        private:      LPCRITICAL_SECTION m_ pCriticalSection;     };    


  使用上也是简单明了:
       void CallFunctionProc()     {       CLock lock(&g_cs);     }  

  但这样完美了么?很遗憾,这只是万里长征的第一步,我们的路还很漫长。最根本的问题,我们如何确保传入的临界区指针是有效的?换句话说,我们无法保证传入的临界区指针是没有调用过DeleteCriticalSection的。
  
  如果代码是如下的书写,很明显,肯定会有我们无法预料的异常:
   void CallFunctionProc()     {     DeleteCriticalSection(&g_cs);     ...            CLock lock(&g_cs);     }   
    
  那有没有这么一种机制,我们可以通过它进入临界区,但我们却不能让它删除临界区,或是即使删除了,我们也有足够的信息获知。
  
  万事无绝对,既然我们能想到,那么我们就能做到。人有多大胆,地有多大产,在这个奇迹倍出的IT界,有时候也并不只是一句华而不实的口号。
  
  首先,我们将创建临界区的操作封装于类中,使用者只能获得创建的序号;然后,如果想进入临界区,只要传入序号即可;最后,如果不需要该临界区,那么传入序号删除即可。这样的好处是,通过序号这个桥梁,既能使用临界区的功能,又不会引发临界区的不稳定。
  
  我们先来看看创建的函数:
  static DWORD CLock::Create()     {       CRITICAL_SECTION* pcsCriticalSection = new CRITICAL_SECTION();      if(pcsCriticalSection == NULL)      {       return Lock::INVALID_INDEX;      }            __try      {       InitializeCriticalSection(pcsCriticalSection);      }      __except(GetExceptionCode() == STATUS_NO_MEMORY)      {       //Failed to intialize the critical section, so delete the object created by new operate.            delete pcsCriticalSection;            return Lock::INVALID_INDEX;      }           return AddTable(pcsCriticalSection);      }   
    
  虽然我们将Create函数封装于CLock类中,但我们不打算需要通过对象才能调用,所以我们以static进行修饰,让其成为类函数。那么,我们调用的时候,就可以直接通过类作用域使用:
  
     DWORD dwIndex = CLock::Create();
  
  我们留意一下这句:
  CRITICAL_SECTION* pcsCriticalSection = new CRITICAL_SECTION(); 
       
  这句代码用来在栈中创建CRITICAL_SECTION对象。之所以这么做,是因为如果使用局部变量,那么在函数返回时,局部变量就被删除。而通过new创建出来的对象,除非我们调用delete,否则在程序运行期中一直存在。这样就能达到创建的目的。
  
  接下来的异常捕获部分可能是大家都会忽略的。根据MSDN的文档,InitializeCriticalSection并不保证绝对能创建成功。当内存不足时,就会抛出STATUS_NO_MEMORY异常。所以我们必须捕获这个异常,并进行相应的处理。
  
  话又说回来,在我们平时直接调用临界区的方式中,如果出现这个异常是很麻烦的事情。因为CRITICAL_SECTION对象的内部细节没有公布,我们无法根据CRITICAL_SECTION对象来确定临界区是否有效,也就无法保证代码的健壮性。
  
  如果一切顺利,我们就通过AddTable将临界区放入存储空间中。
  
  AddTable的实现如下:
  static DWORD CLock::AddTable(CRITICAL_SECTION* pcsCriticalSection)     {       if(ms_pmpCriticalSection == NULL)      {       ms_pmpCriticalSection = new std::map<DWORD,CriticalSectionData>();              if(ms_pmpCriticalSection == NULL)       {        return Lock::INVALID_INDEX;       }       else       {        ms_dwIndex = 0;       }        }           CriticalSectionData newCriticalSectionData = {pcsCriticalSection,0};      InterlockedIncrement(reinterpret_cast<LONG *>(&ms_dwIndex));      ms_pmpCriticalSection->insert(std::make_pair(ms_dwIndex,newCriticalSectionData));           return ms_dwIndex;     }   
    
  ms_pmpCriticalSection是一个成员变量指针,其在头文件声明如下:
  
  static std::map<DWORD,CriticalSectionData> *ms_pmpCriticalSection;
  
  CriticalSectionData是我们定义的一个结构体,有两个成员变量,一个是指向临界区对象,另一个则记录了进入该临界区的次数:  
     struct CriticalSectionData     {      CRITICAL_SECTION *pCriticalSection;      LONG lLockCount;     };
  
  看到这里,可能有的朋友说,CRITICAL_SECTION不是有个LockCount成员么,直接使用它不就行了?从理论上来说,这是可以的。但对于该结构,微软并没有描述文档,换而言之,微软保留了变更其成员的权利。虽然我们可以相信,微软一般不会做这种费力不讨好的事,但谁知道哪天微软又心血来潮呢?所以,与其将代码建立于微软的信任,还不如建立于我们的掌握之中。
  
  我们稍微回头看看,为什么我们定义ms_pmpCriticalSection为指针,而不是直接作为普通变量?也就是说,为什么不直接这样定义:
  static std::map<DWORD,CriticalSectionData> ms_mpCriticalSection; 
       
  如果这样定义,会有很大的风险。因为如果有一个类在构造函数调用了CLock::Create,而该类之后的对象又有一个静态声明,那么就会涉及到静态变量初始化顺序的不可控问题。因为在你调用CLock::Create的时候,ms_mpCriticalSection对象很有可能还有进行初始化。
  
  例如:
     class CTest     {       public:        CText():        m_dwLock(CLock::Create())     {     }          private:     DWORD m_dwLock;     };    
    
     //这里很可能会出错,因为其构造函数调用了CLock::Create,而ms_mpCriticalSection很可能还没有进行初始化     static CTest test;   


  为了避免静态变量初始化导致的问题,我们只能采用new来进行分配。
  
  在这里还有一个小技巧,为了避免繁琐的算法,我们直接采用了STL的map。这样,在后续的查找中,我们就能比较轻松。
  
  既然返回的只是序号,那么我们的构造函数也必然需要进行相应的修改:
  CLock::CLock(DWORD dwIndex,BOOL *pRes = NULL):     m_pcsCriticalSection(NULL)     {      //In order to make the source code simple, set the result as FALSE at first.      if(pRes != NULL)      {       *pRes = FALSE;      }            m_pcsCriticalSection = GetObject(dwIndex);      if(m_pcsCriticalSection != NULL)      {       if(pRes != NULL)       {        *pRes = TRUE;       }              InterlockedIncrement(&m_pcsCriticalSection->lLockCount);       EnterCriticalSection(m_pcsCriticalSection->pCriticalSection);      }      }   
    
  从使用角度来说,我们只需要一个序号的形参即可。但我们想知道能不能成功进入临界区,而构造函数又不能拥有返回值,所以我们就额外多增加了一个pRes形参,用来在函数返回后指明当前进入临界区的状况。
  
  GetObject是我们定义的一个函数,具体实现将在后面说明。现在只需要知道其实返回创建的一个临界区数据。如果获取一个临界区数据对象不为NULL,那么我们在进入临界区之前,先将lLockCount的计数增加1,标明当前进入临界区的次数。
  
  其实很简单,和之前我们的最简单的构造函数相比,只是传入的形参不同。而恰恰只是这一点不同,就能使我们的代码临界区有一个质的飞跃。
  
  再转回头,我们看看GetObject的实现:
          CLock::CriticalSectionData* CLock::GetObject(DWORD dwIndex)     {      if(ms_pmpCriticalSection == NULL)      {       ASSERT(FALSE);       return NULL;      }           std::map<DWORD,CriticalSectionData>::iterator iter = ms_pmpCriticalSection->find(dwIndex);      if(iter != ms_pmpCriticalSection->end())      {       return &iter->second;        }      else      {       return NULL;      }          }  


  代码并不复杂,只是从ms_pmpCriticalSection搜索出对应序号的数值,然后返回。如果该序号不存在,则返回NULL。
  
  到这里,使用上的功能我们基本上已经完成。但似乎还缺了点什么,对,没错,还缺删除函数。删除函数并不复杂,删除临界区后,然后调用delete删除创建的栈,最后再从ms_pmpCriticalSection中删掉相应的序号即可。但我们需要考虑到使用者的便利性,不仅可以删除对应序号的对象,还能简单地删除所有。所以,对于删除函数,我们定义两个。而这两个删除函数,都应该通过不同的手法调用这个内部删除函数:
   static BOOL CLock::Delete(const std::map<DWORD,CriticalSectionData>::iterator &iter)     {      if(ms_pmpCriticalSection == NULL)      {       ASSERT(FALSE);       return FALSE;      }           if(iter == ms_pmpCriticalSection->end())      {       return FALSE;      }           if(GetLockCount(iter->first) != 0)      {         ASSERT(FALSE);       return FALSE;      }            if(iter->second.pCriticalSection == NULL)      {       return FALSE;      }           DeleteCriticalSection(iter->second.pCriticalSection);      delete iter->second.pCriticalSection;            return TRUE;     }   
    
  形参是传入一个迭代器,然后根据该迭代器做相应的操作。
  
  那么,我们public的删除相应序号的函数可以如下:  
     BOOL CLock::Delete(DWORD dwIndex)     {      if(ms_pmpCriticalSection == NULL)      {       ASSERT(FALSE);       return FALSE;      }           std::map<DWORD,CriticalSectionData>::iterator iter = ms_pmpCriticalSection->find(dwIndex);      if(Delete(iter) != FALSE)      {       ms_pmpCriticalSection->erase(iter);            CheckAndDeleteCriticalSection();            return TRUE;      }      else      {       return FALSE;      }          }  
  函数过程简单明了,从通过find函数查找相应key的位置,然后传递给Delete函数。如果Delete返回值为TRUE,那么我们就通过erase擦除。我们之所以没有在Delete(const std::map<DWORD,CriticalSectionData>::iterator &iter)函数中进行擦除,是因为map的迭代器,只要一删除,就会失效,这对后续的操作有所不利。所以,我们只能根据其返回值再确定是否删除。
  
  CheckAndDeleteCriticalSection()函数用来判断ms_pmpCriticalSection是否为空,如果为空,直接调用delete删除该指针:  
     static void CLock::CheckAndDeleteCriticalSection()     {      if(ms_pmpCriticalSection->empty() != FALSE)      {       delete ms_pmpCriticalSection;       ms_pmpCriticalSection = NULL;      }     }
  
  有了这些基础,那么我们的删除所有的函数就简单了:
   static void CLock::DeleteAll()     {      if(ms_pmpCriticalSection == NULL)      {       ASSERT(FALSE);       return;      }           for(std::map<DWORD,CriticalSectionData>::iterator iter = ms_pmpCriticalSection->begin(); iter != ms_pmpCriticalSection->end(); )      {       //The iterator would be destroy when you call erase,so I must call just as follows.       if(Delete(iter) != FALSE)       {        ms_pmpCriticalSection->erase(iter ++);       }       else       {        ++ iter;       }      }            CheckAndDeleteCriticalSection();          }   
    
  首先我们判断ms_pmpCriticalSection是否为NULL,为NULL则什么都不做,返回。然后,再遍历整个存储空间,传入其相应的迭代器进行操作。如果Delete返回不为FALSE,我们就调用erase进行删除。
  
  在这里一个小细节需要留意,就是这段代码:
  for(std::map<DWORD,CriticalSectionData>::iterator iter = ms_pmpCriticalSection->begin(); iter != ms_pmpCriticalSection->end(); )      {       //The iterator would be destroy when you call erase,so I must call just as follows.       if(Delete(iter) != FALSE)       {        ms_pmpCriticalSection->erase(iter ++);       }       else       {        ++ iter;       }      }       
    
  对于这段代码,我们不能这么改写:
  for(std::map<DWORD,CriticalSectionData>::iterator iter = ms_pmpCriticalSection->begin(); iter != ms_pmpCriticalSection->end();++ iter )      {       //The iterator would be destroy when you call erase,so I must call just as follows.       if(Delete(iter) != FALSE)       {        ms_pmpCriticalSection->erase(iter);       }             } 
    
  因为我们知道,map容器只要进行erase操作,那么相应的迭代器就会失效。如果采用的是后面一种写法,那么我们调用erase操作后,iter就已经无效,循环中的iter就变成一个不可预料的操作。
  
  现在,就是完整罗列代码的时候了:
  
  头文件:
  #pragma once          #include "Windows.h"     #include <map>          namespace Lock     {      const DWORD INVALID_INDEX = 0xFFFFFFFF;     };          class CLock     {     public:      //----------------------------------------------------------------------------------------      //Description:      //   Create the lock object and return the index      //Return Values:      // If the value is INVALID_INDEX, it means failed. Others is succeeded.      //-----------------------------------------------------------------------------------------      static DWORD Create();           //---------------------------------------------------------------------------------------------      //Description:      //   Delete all the created critical section.When you call the function,you must be sure that      //there isn't other threads to enter the critical section.After succeed in calling the function,      //the GetSize() returns 0, otherwise it doesn't delete all the critical section object.      //---------------------------------------------------------------------------------------------      static void DeleteAll();            //---------------------------------------------------------------------------------------------      //Description:      //   Delete the created critical section on the index.      //Parameters:      // dwIndex : [in]The index of object to delete      //Return Values:      // FALSE means failed, maybe the index is locked.      //---------------------------------------------------------------------------------------------      static BOOL Delete(DWORD dwIndex);            //----------------------------------------------------------------------------------------      //Description:      //   Get the size of the the critical section      //----------------------------------------------------------------------------------------      static DWORD GetSize();           //------------------------------------------------------------------------------------------------      //Description:      //   Get the lock count      //Parameters:      // dwIndex : [in] The index to check.      //Return Values:      // 0 means there is not the thread to lock the index of object or the index if invalid      //Others means the count of lock      //---------------------------------------------------------------------------------------------------      static LONG GetLockCount(DWORD dwIndex);           //----------------------------------------------------------------------------------------      //Description:      //   The construct is used for enter the critical section      //Parameters:      // dwIndex : [in] The index to lock      // pRes : [in] The result of locking. TRUE means succeeded, FALSE means failed.      //    If you don't want this value,you could set NULL.      //----------------------------------------------------------------------------------------      CLock(DWORD dwIndex,BOOL *pRes = NULL);           //----------------------------------------------------------------------------------------      //Description:      //   The destruct is used for leave the critical section      //----------------------------------------------------------------------------------------      virtual ~CLock();          private:      //----------------------------------------------------------------------------------------      //Description:      //   Add the critical section object to the table.      //Parameters:      // pcsCriticalSection : [in] The object to add.      //Parameters:      // Return the index in the table.      //--------------------------------------------------------------------------------------      static DWORD AddTable(CRITICAL_SECTION* pcsCriticalSection);                //----------------------------------------------------------------------------------------      //Description:      //  The struct value is for the internal value      //----------------------------------------------------------------------------------------      struct CriticalSectionData      {       CRITICAL_SECTION *pCriticalSection;       LONG lLockCount;      };           //----------------------------------------------------------------------------------------      //Description:      //  Delete the object base on the iterator positon      //Parameters:      // iter : [in] The iterator to delete      //-----------------------------------------------------------------------------------------      static BOOL Delete(const std::map<DWORD,CriticalSectionData>::iterator &iter);           //----------------------------------------------------------------------------------------      //Description:      //   Get the critical section object      //Parameters:      // dwIndex : [in] The index to get.      //Return Values:      // NULL means failed,others is succeeded.      //----------------------------------------------------------------------------------------      static CriticalSectionData* GetObject(DWORD dwIndex);           //----------------------------------------------------------------------------------------      //Description:      //   Check and delete the critical section which is allocate by new operate.      //----------------------------------------------------------------------------------------      static void CheckAndDeleteCriticalSection();          private:       static std::map<DWORD,CriticalSectionData> *ms_pmpCriticalSection;      static DWORD ms_dwIndex;      CriticalSectionData *m_pcsCriticalSection;     };  
  
  .cpp实现文件:
    #include "Lock.h"          //----------------------------------------------------------------------------------     //The static member     std::map<DWORD,CLock::CriticalSectionData> * CLock::ms_pmpCriticalSection = NULL;     DWORD CLock::ms_dwIndex = 0;     //----------------------------------------------------------------------------------          CLock::CLock(DWORD dwIndex,BOOL *pRes):     m_pcsCriticalSection(NULL)     {      //In order to make the source code simple, set the result as FALSE at first.      if(pRes != NULL)      {       *pRes = FALSE;      }            m_pcsCriticalSection = GetObject(dwIndex);      if(m_pcsCriticalSection != NULL)      {       if(pRes != NULL)       {        *pRes = TRUE;       }              InterlockedIncrement(&m_pcsCriticalSection->lLockCount);       EnterCriticalSection(m_pcsCriticalSection->pCriticalSection);      }      }          CLock::~CLock()     {      if(m_pcsCriticalSection != NULL)      {       LeaveCriticalSection(m_pcsCriticalSection->pCriticalSection);       InterlockedDecrement(&m_pcsCriticalSection->lLockCount);      }     }          DWORD CLock::Create()     {       CRITICAL_SECTION* pcsCriticalSection = new CRITICAL_SECTION();      if(pcsCriticalSection == NULL)      {       return Lock::INVALID_INDEX;      }            __try      {       InitializeCriticalSection(pcsCriticalSection);      }      __except(GetExceptionCode() == STATUS_NO_MEMORY)      {       //Failed to intialize the critical section, so delete the object created by new operate.            delete pcsCriticalSection;            return Lock::INVALID_INDEX;      }           return AddTable(pcsCriticalSection);      }          void CLock::DeleteAll()     {      if(ms_pmpCriticalSection == NULL)      {       ASSERT(FALSE);       return;      }           for(std::map<DWORD,CriticalSectionData>::iterator iter = ms_pmpCriticalSection->begin(); iter != ms_pmpCriticalSection->end(); )      {       //The iterator would be destroy when you call erase,so I must call just as follows.       if(Delete(iter) != FALSE)       {        ms_pmpCriticalSection->erase(iter ++);       }       else       {        ++ iter;       }      }            CheckAndDeleteCriticalSection();          }          CLock::CriticalSectionData* CLock::GetObject(DWORD dwIndex)     {      if(ms_pmpCriticalSection == NULL)      {       ASSERT(FALSE);       return NULL;      }           std::map<DWORD,CriticalSectionData>::iterator iter = ms_pmpCriticalSection->find(dwIndex);      if(iter != ms_pmpCriticalSection->end())      {       return &iter->second;        }      else      {       return NULL;      }          }          DWORD CLock::GetSize()     {      if(ms_pmpCriticalSection == NULL)      {       ASSERT(FALSE);       return 0;      }           return ms_pmpCriticalSection->size();     }                    DWORD CLock::AddTable(CRITICAL_SECTION* pcsCriticalSection)     {       if(ms_pmpCriticalSection == NULL)      {       ms_pmpCriticalSection = new std::map<DWORD,CriticalSectionData>();              if(ms_pmpCriticalSection == NULL)       {        return Lock::INVALID_INDEX;       }       else       {        ms_dwIndex = 0;       }        }           CriticalSectionData newCriticalSectionData = {pcsCriticalSection,0};      InterlockedIncrement(reinterpret_cast<LONG *>(&ms_dwIndex));      ms_pmpCriticalSection->insert(std::make_pair(ms_dwIndex,newCriticalSectionData));           return ms_dwIndex;     }          BOOL CLock::Delete(DWORD dwIndex)     {      if(ms_pmpCriticalSection == NULL)      {       ASSERT(FALSE);       return FALSE;      }           std::map<DWORD,CriticalSectionData>::iterator iter = ms_pmpCriticalSection->find(dwIndex);      if(Delete(iter) != FALSE)      {       ms_pmpCriticalSection->erase(iter);            CheckAndDeleteCriticalSection();            return TRUE;      }      else      {       return FALSE;      }          }          BOOL CLock::Delete(const std::map<DWORD,CriticalSectionData>::iterator &iter)     {      if(ms_pmpCriticalSection == NULL)      {       ASSERT(FALSE);       return FALSE;      }           if(iter == ms_pmpCriticalSection->end())      {       return FALSE;      }           if(GetLockCount(iter->first) != 0)      {         ASSERT(FALSE);       return FALSE;      }            if(iter->second.pCriticalSection == NULL)      {       return FALSE;      }           DeleteCriticalSection(iter->second.pCriticalSection);      delete iter->second.pCriticalSection;            return TRUE;     }          LONG CLock::GetLockCount(DWORD dwIndex)     {      CriticalSectionData *pCriticalSection = GetObject(dwIndex);      if(pCriticalSection == NULL)      {       return 0;      }      else      {       return pCriticalSection->lLockCount;      }     }          void CLock::CheckAndDeleteCriticalSection()     {      if(ms_pmpCriticalSection->empty() != FALSE)      {       delete ms_pmpCriticalSection;       ms_pmpCriticalSection = NULL;      }     }  


  简单地调用示范如下:
       //获取相应的序号     const DWORD dwIndex = CLock::Create();          //进入临界区     CLock lock(dwIndex);          //删除相应的临界区     CLock::Delete(dwIndex);

           

分享一下我老师大神的人工智能教程。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.youkuaiyun.com/jiangjunshow

Free RTOS学习随笔(1),临界区代码 Free RTOS学习随笔(1),临界区代码基本介绍Free RTOS中临界区代码常用函数任务级临界代码保护调用方式实现原理中断级临界代码保护调用方式实现原理 基本介绍 临界区代码指的是那些必须完整执行、不能被打断的代码,比如在初始化一些外设的时候,需要按照严格的时序进行,这样的代码是不能被中断打断的。 Free RTOS在进入临界区代码时,需要关闭中断,当临界区代码完整执行后再打开中断。 临界区代码一定要尽可能精简,进入临界区关闭中断后,优先级低于configMAX_SYSCALL_INTERRUPT_P 阅读详情

相关推荐

DIY必备:如何用N-MOS管搭建低成本DC有刷电机驱动电路(附原理图+避坑指南)

本文详细介绍了如何使用N-MOS管搭建低成本、高可靠性的DC有刷电机驱动电路。针对DIY爱好者,从核心原理、电路设计、元件选型到焊接调试,提供了完整的实战指南,并重点解析了续流二极管选型、栅极驱动设计等关键避坑要点,帮助读者成功驱动电机并确保电路稳定运行。

delta的博客 776

实用!超强VC/MFC 常见问答收集

 问  自编浏览器进入一个网页后,点一个链接后系统自动调用用IE打开网页而不是用自身浏览器打开网页。如何让窗口用我自己的浏览器打开?  答:  控制新的窗口  默认情况下,浏览器收到创建新窗口请求时,会在IE中打开新的窗口。你可以处理NewWindow2事件来在自己指定的窗口中打开请求的页面。  问:  如何枚举系统中视频捕获设备(摄像头)的设备名称  答:  以下代码来 自DirectX9

1238

冰河木马实验(V8.4)

简述: 百科词条 冰河木马开发于1999年,跟灰鸽子类似,在设计之初,开发者的本意是编写一个功能强大的远程控制软件。但一经推出,就依靠其强大的功能成为了黑客们发动入侵的工具,并结束了国外木马一统天下的局面,跟后来的灰鸽子等等成为国产木马的标志和代名词。 冰河的服务器端程序为G-server.exe,客户端程序为G-client.exe,默认连接端口为7626。其中攻击者使用G-client.e...

野原新之助 1万+

21 内核开发-临界区及临界区代码段判断

临界区是计算机系统中的一段代码,在任何时刻只能被一个线程执行。临界区的目的是防止多个线程同时访问共享资源,从而避免数据损坏或其他问题。互斥:在任何时刻,只能有一个线程执行临界区中的代码。有限等待:一个线程不能无限期地等待进入临界区。如果临界区被其他线程占用,等待的线程必须在有限的时间内获得访问权。临界区代码指的是一段代码路径,代码或者系统中必须同时满足以下两个条件才能称得上是临界区代码条件一:代码路径可能是并发的,也就是说,存在它可以并行运行的可能性。并且。

jxusthusiwen的专栏 1958

使用扫频信号测量房间脉冲响应

脉冲响应的测量

liuderong0的博客 1765

别再只会用Kriging了!用Python+GeoPandas手把手实现IDW插值(附完整代码与可视化)

本文详细介绍了如何使用Python和GeoPandas实现反距离加权插值(IDW),包括核心算法、参数调优和可视化技巧。通过完整代码示例,帮助读者掌握这一空间数据分析技术,适用于环境监测、气象预报等多个领域,提升数据处理效率和灵活性。

weixin_30872157的博客 227

告别Fetch的噩梦!5款神器让你的前端代码瞬间优雅,效率飙升300%!

定位:专为JSONP设计(老项目兼容),但不推荐新项目使用!为什么提它:有些老接口只支持JSONP,避免你被坑。代码示例⚠️注意事项:JSONP有安全风险(XSS),新项目优先用Axios/SWR解决跨域。

JakeMa1024的博客 1255

二进制、八进制、十六进制、十进制转换详解

二进制是计算机的语言,正如我们使用汉语表达与交流信息一样,任何信息在计算机中都以二进制的形式存储和计算。了解二进制与十进制等进制之间的转换方法,有助于我们了解计算机内部的数字计算过程,可以帮助我们在一定程度上理解信息在计算机中的编码和解码过程。本文先简单介绍位置化数字系统的概念,然后详细讲解二进制、八进制、十六进制、十进制之间的转换方法与原理。

2301_81674797的博客 2097

告别兼容性噩梦:Bootstrap 5与jQuery的优雅共存方案

你是否曾为老项目升级Bootstrap时的兼容性问题头疼?是否在现代前端框架与传统jQuery插件间徘徊不定?本文将揭示Bootstrap 5如何实现与jQuery的和平共处,让你既能享受现代前端开发的便利,又无需重构历史代码。读完本文,你将掌握: - Bootstrap 5与jQuery的技术衔接点 - 三种混合开发模式的实战配置 - 性能优化的关键技巧 - 官方测试用例解析与最佳实践 ##...

gitblog_00797的博客 856

黑科技已上线|Lazada数据分析软件,一键式搞定你的店铺!

工欲利其事,必先利其器。在lazada跨境电商的整个流程中,适当的使用一些工具,不仅能大大的简化工作流程,而且可以提高实施效率。Mengla专为lazada电商卖家行业精细化分析lazada数据分析软件都有哪些强大功能! 一、两大优势: 1.海量数据: 数据覆盖面积大;数据分析维度逛;精确算法抓取海量数据;亿万数据秒级响应多种数据来源; 2.技术成熟: 深耕SaaS大数据挖掘;提供一站式数据分析服务;拥有领先的采集技术和运算模型架构; 二、Lazada数据分析软件的功能: 1.各国站点一键分析: 马来西亚

Menglakeji的博客 1146

Spine动画在Unity里卡顿、闪烁、排序错乱?这些性能优化与渲染问题一次讲清

本文深入解析了Spine动画在Unity中常见的卡顿、闪烁和排序错乱问题,提供了全面的性能优化与渲染解决方案。从性能瓶颈诊断到材质合并、动态合批优化,再到层级错乱与Alpha闪烁的处理,帮助开发者提升动画表现和运行效率。特别针对Unity和Spine的协同工作进行了详细的技术探讨。

weixin_29967445的博客 382

看看人家写的,Controller太优雅了~

通过使用@Valid注解、参数校验注解、全局异常处理等技巧,我们不仅可以提高代码的可读性和维护性,还能避免写出让人血压飙升的Controller。很多时候,程序员喜欢在Controller里直接写业务逻辑,看起来是方便了,但实际上却为代码的扩展性和可维护性埋下了隐患。使用这些注解,可以让我们更方便地进行输入校验,并减少重复的校验代码。通过这种方式,不仅减少了代码量,也提高了可读性,让你的Controller更加干净整洁。通过这种方式,我们能优雅地处理所有的校验异常和系统异常,大大提高了代码的可维护性。

java_121388的博客 360

CXL ALMP(ARB/MUX Link Management Packet)理解

ALMP(ARB/MUX Link Management Packet) 是CXL协议中由ARB/MUX层生成和处理的专用管理报文,用于协调链路电源状态切换(如L0s/L1)和虚拟链路状态机(vLSM)同步。ALMP是ARB/MUX层的控制平面报文,专用于以下场景:协调多协议(CXL.io、CXL.cachemem等)的链路状态切换(如L1进入/退出)。维护各协议独立的虚拟链路状态机(vLSM) 状态同步。在物理层(PHY)与链路层(Link Layer)之间仲裁电源管理请求。

Summer的博客 1198

别让线程中断成为噩梦:Java优雅中断实战攻略

对中断异常的处理不应仅仅是记录错误日志,它应当妥善地重新设置中断标志,或者根据程序的逻辑需要,采取适当的行动。在多线程编程中,理解如何正确响应中断是编写健壯、响应灵敏的Java应用程序的关键所在。

逆流的小鱼 1098

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

Reducer:我从不会写纯函数到写出优雅代码的血泪史

为什么Reducer一定要是纯函数?直接修改state不行吗?

m0_75220683的博客 986

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

告别JS初学者噩梦:这样写流程控制和函数才叫优雅

写代码就像写文章,好的代码应该是清晰、优雅、易于理解的。通过合理的流程控制和函数封装,我们不仅能提高开发效率,还能让代码更易于维护和协作。记住,代码首先是写给人看的,其次才是给机器执行的。你现在写的代码,可能半年后就要由别人(甚至你自己)来维护。多花几分钟思考如何让代码更清晰,将来可能节省几小时的调试时间。你在实际开发中还遇到过哪些流程控制或函数封装的难题?欢迎在评论区分享,我们一起探讨更好的解决方案!

在0与1的裂缝里,种出AI的未来。 764

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

避免中断!Spring Boot + Kubernetes 优雅停机深度实战:终结 InterruptedException 与 SIGKILL 噩梦的生产级保障

摘要: Spring Boot服务在Kubernetes中部署时,常因Pod终止导致请求中断和资源未释放问题。本文剖析了InterruptedException与SIGKILL的根本原因:Spring Boot默认immediate关闭模式与K8sterminationGracePeriodSeconds配置不协调。通过模拟测试发现,未配置优雅停机时,耗时请求会被强制中断。解决方案需结合server.shutdown=graceful和合理设置terminationGracePeriodSeconds,确保

lyd8483789的博客 819

C++20协程从异步编程的噩梦到优雅并发的救赎

对于任何类型,只要它实现了`await_ready`、`await_suspend`和`await_resume`这三个必要的接口,就可以被`co_await`等待。C++20协程的核心在于三个关键组件:`co_await`、`co_yield`和`co_return`运算符,以及围绕它们的定制点。它并非某种具体类型,而是一种语言层面的强大框架,允许函数在执行过程中被挂起,并在之后恢复执行。与需要独立调用栈的有栈协程相比,无栈协程的开销通常更小,因为它们的状态存储在堆上单独分配的对象中,而不是整个栈。

fog1111的博客 387

从会写代码到写好代码:程序员进阶的核心修炼指南

这篇文章 能帮助开发者系统性提升代码质量。如果你有具体的代码案例想进行优化分析,或者想针对某一编程语言(如 Java、Python、前端)展开更细致的讲解,都可以告诉我,我会进一步补充完善内容!

weixin_56334307的博客 1394
上一篇: IImage和链接错误
下一篇: 年底好书真多-- Windows via C/C++
小树林后面
博客等级 码龄8年 21粉丝 248原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值