Win32环境下两种用于C++的线程同步类

2023-10-27

线程同步是多线程程序设计的核心内容,它的目的是正确处理多线程并发时的各种问题,例如线程的等待、多个线程访问同一数据时的互斥,防死锁等。Win32提供多种内核对象和手段用于线程同步,如互斥量、信号量、事件、临界区等。所不同的是,互斥量、信号量、事件都是Windows的内核对象,当程序对这些对象进行控制时会自动转换到核心态,而临界区本身不是内核对象,它是工作在用户态的。我们知道从用户态转换到核心态是需要以时间为代价的,所以如果能在用户态就简单解决的问题,就可以不必劳烦核心态了。
这里我要说的是两种用于C++的多线程同步类,通过对这两种类的使用就可以方便的实现对变量或代码段的加锁控制,从而防止多线程对变量不正确的操作。
所谓加锁,就是说当我们要访问某关键变量之前,都需要首先获得允许才能继续,如果未获得允许则只有等待。一个关键变量拥有一把锁,一个线程必须先得到这把锁(其实称为钥匙可能更形象)才可以访问这个变量,而当某个变量持有这把锁的时候,其他线程就不能重复的得到它,只有等持有锁的线程把锁归还以后其他线程才有可能得到它。之所以这样做,就是为了防止一个线程读取某对象途中另一线程对它进行了修改,或两线程同时对一变量进行修改,例如:
// 全局:
        struct  MyStruct  ... int a, b; } ;
       MyStruct s;
// 线程1:
        int  a  =  s.a;
       
int  b  =  s.b;
// 线程2:
       s.a ++ ;
       s.b
-- ;
如果实际的执行顺序就是上述书写的顺序那到没有什么,但如果线程2的执行打断了线程1,变为如下顺序:
       int a = s.a;      //线程1
       s.a++;            //线程2
       s.b++;            //线程2
       int b = s.b;      //线程1
那么这时线程1读出来的a和b就会有问题了,因为a是在修改前读的,而b是在修改后读的,这样读出来的是不完整的数据,会对程序带来不可预料的后果。天知道两个程的调度顺序是什么样的。为了防止这种情况的出现,需要对变量s加锁,也就是当线程1得到锁以后就可以放心的访问s,这时如果线程2要修改s,只有等线程1访问完成以后将锁释放才可以,从而保证了上述两线程交叉访问变量的情况不会出现。
使用Win32提供的临界区可以方便的实现这种锁:
// 全局:
       CRITICAL_SECTION cs;
       InitializeCriticalSection(
& cs);
// 线程1:
       EnterCriticalSection( & cs);
       
int  a  =  s.a;
       
int  b  =  s.b;
       LeaveCriticalSection(
& cs);
// 线程2:
       EnterCriticalSection( & cs);
       s.a
++ ;
       s.b
-- ;
       LeaveCriticalSection(
& cs);
// 最后:
       DeleteCriticalSection( & cs);
代码中的临界区变量(cs)就可以看作是变量s的锁,当函数EnterCriticalSection返回时,当前线程就获得了这把锁,之后就是对变量的访问了。访问完成后,调用LeaveCriticalSection表示释放这把锁,允许其他线程继续使用它。
如果每当需要对一个变量进行加锁时都需要做这些操作,显得有些麻烦,而且变量cs与s只有逻辑上的锁关系,在语法上没有什么联系,这对于锁的管理带来了不小的麻烦。程序员总是最懒的,可以想出各种偷懒的办法来解决问题,例如让被锁的变量与加锁的变量形成物理上的联系,使得锁变量成为被锁变量不可分割的一部分,这听起来是个好主意。
首先想到的是把锁封闭在一个类里,让类的构造函数和析构函数来管理对锁的初始化和锁毁动作,我们称这个锁为“实例锁”:
        class  InstanceLockBase
       
... {
              CRITICAL_SECTION cs;
       
protected:
              InstanceLockBase() 
...{ InitialCriticalSection(&cs); }
              
~InstanceLockBase() ...{ DeleteCriticalSection(&cs); }
       }
;
如果熟悉C++,看到这里一定知道后面我要干什么了,对了,就是继承,因为我把构造函数和析构函数都声明为保护的(protected),这样唯一的作用就是在子类里使用它。让我们的被保护数据从这个类继承,那么它们不就不可分割了吗:
        struct  MyStruct:  public  InstanceLockBase
       
... { … } ;
什么?结构体还能从类继承?当然,C++中结构体和类除了成员的默认访问控制不同外没有什么不一样,class能做的struct也能做。此外,也许你还会问,如果被锁的是个简单类型,不能继承怎么办,那么要么用一个类对这个简单类型进行封装(记得Java里有int和Integer吗),要么只好手工管理它们的联系了。如果被锁类已经有了基类呢?没关系,C++是允许多继承的,多一个基类也没什么。
现在我们的数据里面已经包含一把锁了,之后就是要添加加锁和解锁的动作,把它们作为InstanceLockBase类的成员函数再合适不过了:
        class  InstanceLockBase
       
... {
              CRITICAL_SECTION cs;
              
void Lock() ...{ EnterCriticalSection(&cs); }
              
void Unlock() ...{ LeaveCriticalSection(&cs); }
              …
}
;
看到这里可能会发现,我把Lock和Unlock函数都声明为私有了,那么如何访问这两个函数呢?是的,我们总是需要有一个地方来调用这两个函数以实现加锁和解锁的,而且它们总应该成对出现,但C++语法本身没能限制我们必须成对的调用两个函数,如果加完锁忘了解,那后果是严重的。这里有一个例外,就是C++对于构造函数和析构函数的调用是自动成对的,对了,那就把对Lock和Unlock的调用专门写在一个类的构造函数和析构函数中:
class  InstanceLock
... {
              InstanceLockBase
* _pObj;
public:
              InstanceLock(InstanceLockBase
* pObj)
              
...{
                     _pObj 
= pObj; //这里会保存一份指向s的指针,用于解锁
                     if(NULL != _pObj)
                            _pObj
->Lock();      //这里加锁
              }

              
~InstanceLock()
              
...{
                     
if(NULL != _pObj)
                            _pObj
->Unlock();   //这里解锁
              }

}
;
最后别忘了在类InstanceLockBase中把InstanceLock声明为友元,使得它能正确访问Lock和Unlock这两个私有函数:
class  InstanceLockBase
... {
              friend 
class InstanceLock;
              …
}
;
好了,有了上面的基础,现在对变量s的加解锁管理变成了对InstanceLock的实例的生命周期的管理了。假如我们有一个函数ModifyS中要对s进行修改,那么只要在函数一开始就声明一个InstaceLock的实例,这样整个函数就自动对s加锁,一旦进入这个函数,其他线程就都不能获得s的锁了:
        void  ModifyS()
       
... {
              InstanceLock 
lock(&s);        //这里已经实现加锁了
              
//some operations on s
       }
    
                                           //一旦离开lock对象的作用域,自动解锁
如果是要对某函数中一部分代码加锁,只要用一对大括号把它们括起来再声明一个lock就可以了:
       …
       
... {
              InstanceLock 
lock(&s);
              
// do something …
       }

       …
好了,就是这么简单。下面来看一个测试。
首先准备一个输出函数,对我们理解程序有帮助。它会在输出我们想输出的内容同时打出行号和时间:
void  Say( char *  text)
... {
              
static int count = 0;
              SYSTEMTIME st;
              ::GetLocalTime(
&st);
              printf(
"%03d [%02d:%02d:%02d.%03d]%s "++count, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, text);
}
当然,原则上当多线程都调用这个函数时应该对其静态局部变量count进行加锁,这里就省略了。
我们声明一个非常简单的被锁的类型,并生成一个实例:
class  MyClass:  public  InstanceLockBase
... {} ;
MyClass mc;
子线程的任务就是对这个对象加锁,然后输出一些信息:
DWORD CALLBACK ThreadProc(LPVOID param)
... {
              InstanceLock il(
&mc);
              Say(
"in sub thread, lock");
              Sleep(
2000);
              Say(
"in sub thread, unlock");
              
return 0;
}
这里会输出两条信息,一是在刚刚获得锁的时间,二是在释放锁的时候,中间通过Sleep来延迟2秒。
主线程负责开启子线程,然后也对mc加锁:
CreateThread( 0 0 , ThreadProc,  0 0 0 );
       
... {
              InstanceLock il(
&mc);
              Say(
"in main thread, lock");
              Sleep(
3000);
              Say(
"in main thread, lock");
       }
运行此程序,得到的输出如下:
001 [13:43:23.781]in main thread, lock
002 [13:43:26.781]in main thread, lock
003 [13:43:26.781]in sub thread, lock
004 [13:43:28.781]in sub thread, unlock
从其输出的行号和时间可以清楚的看到两个线程间的互斥:当主线程恰好首先获得锁时,它会延迟3秒,然后释放锁,之后子线程才得以继续进行。这个例子也证明我们的类工作的很好。
总结一下,要使用InstanceLock系列类,要做的就是:
1 让被锁类从InstanceLockBase继承
2 所有要访问被锁对象的代码前面声明InstanceLock的实例,并传入被锁对象的指针。
 
附:完整源代码:
#pragma  once
#include 
< windows.h >
 
class  InstanceLock;
 
class  InstanceLockBase
... {
       friend 
class InstanceLock;
 
       CRITICAL_SECTION cs;
 
       
void Lock()
       
...{
              ::EnterCriticalSection(
&cs);
       }

 
       
void Unlock()
       
...{
              ::LeaveCriticalSection(
&cs);
       }

 
protected:
       InstanceLockBase()
       
...{
              ::InitializeCriticalSection(
&cs);
       }

 
       
~InstanceLockBase()
       
...{
              ::DeleteCriticalSection(
&cs);
       }

}
;
 
class  InstanceLock
... {
       InstanceLockBase
* _pObj;
public:
       InstanceLock(InstanceLockBase
* pObj)
       
...{
              _pObj 
= pObj;
              
if(NULL != _pObj)
                     _pObj
->Lock();
       }

 
       
~InstanceLock()
       
...{
              
if(NULL != _pObj)
                     _pObj
->Unlock();
       }

}
;

 

上一篇中我介绍了一种通过封闭Critical Section对象而方便的使用互斥锁的方式,文中所有的例子是两个线程对同一数据一读一写,因此需要让它们在这里互斥,不能同时访问。而在实际情况中可能会有更复杂的情况出现,就是多个线程访问同一数据,一部分是读,一部分是写。我们知道只有读-写或写-写同时进行时可能会出现问题,而读-读则可以同时进行,因为它们不会对数据进行修改,所以也有必要在C++中封装一种方便的允许读-读并发、读-写与写-写互斥的锁。要实现这种锁,使用临界区就很困难了,不如改用内核对象,这里我使用的是互斥量(Mutex)。
总体的结构与上一篇中的类似,都是写出一个对锁进行封装的基类,再写一个用于调用加、解锁函数的类,通过对第二个类的生命周期的管理实现加锁和解锁。这里涉及到两个新问题,一是加锁、解锁动作都有两种,一种是加/解读锁,一种是加/解写锁;二是为了允许读-读并发,这里只声明一个Mutex是不够的,必须要声明多个Mutex,而且有多少个Mutex就同时允许多少个读线程并发,之所以这么说,是因为我们要使用的API函数是WaitForMultipleObjects。
WaitForMultipleObjects函数的功能就是等待对象状态被设置,MSDN中对它的说明为:
Waits until one or all of the specified objects are in the signaled state or the time-out interval elapses.
这是个很好用的函数,我们可以用它来等待某个或某几个对象,并且允许设置超时时间,等待成功时与超时时返回的值是不同的。如果返回的值比WAIT_ABANDONED小则表示等待成功。“等待成功”对于不同类型的内核对象有不同的意义,例如对于进程或线程对象,等待成功就表示进程或线程执行结束了;对于互斥量对象,则表示此对象现在不被任何其他线程拥有,并且一旦等待成功,当前线程即拥有了此互斥量,其他线程则不能同时拥有,直接调用ReleaseMutex函数主动释放互斥量。
与WaitForMultipleObjects类似的还有一个函数WaitForSingleObject,它的功能比较简单,只针对单一个对象,而WaitForMultipleObjects可以同时等待多个对象,并且可以设置是否等待所有对象。
上一篇文章中用的InstanceLockBase类里面封装了一个Critical Section对象,这里则要封装一组Mutex的Handle,那么这一组是多少个呢?它应该由使用此类的程序中定义,例如可以用动态数组的方法:
// 基类:
        class  RWLockBase  // 表示Read/Write Lock
        ... {
              HANDLE
* handles;
       
protected:
              RWLockBase(
int handleCount) ...{ handles = new HANDLE[handleCount]; }
              …
       }
;
// 子类:
        class  MyClass:  public  RWLockBase      
       
... {
              MyClass(): RWLockBase(
3...{}
              …
       }
;
这确实是个不错的办法,通过在子类构造函数的初始化段中调用基类构造函数并传参,使得这个动态数组得以正确初始化,不过这样看着不太爽,子类必须两次出现“RWLockBase”一词,能不能像InstanceLockBase那样只要继承了就好呢?答案是肯定的,只要用C++模板即可:
       template  < int  maxReadCount >
       
class  RWLockBase
       
... {
              HANDLE handles[maxReadCount];
              …
       }
;
使用模板附带这么一个好处,因为模板参数是在编译期可以确定的,所以无需再用动态数组,直接在栈上分配即可。而使用模板引出一个新问题,就是相应的Lock类(RWLock)在构造时传的对象指针时的类型声明,直接写成RWLock(RWLockBase* pObj)肯定是不行的,因为必须指定模板参数,并且其值还必须与声明RWLockBase时所指定的值一致才行,从而客户端代码就必须两次指定模板参数值,不爽!解决的办法也是有一个,就是把RWLockBase变成夹层类,为它再声明一个基类,让RWLock接收的是基类指针,并把Lock、Unlock等函数放在基类中,声明为纯虚函数,实现写在夹层类中:
class  _RWLockBase
... {
       friend 
class RWLock;
protected:
       
virtual DWORD ReadLock(int timeout) = 0;
       
virtual void ReadUnlock(int handleIndex) = 0;
       
virtual DWORD WriteLock(int timeout) = 0;
       
virtual void WriteUnlock() = 0;
}
;
模板类RWLockBase从_RWLockBase继承,并对四个函数写出实现:
template  < int  maxReadCount  =   3 >          // 这里给一个缺省参数,尽量减少客户端代码量
class  RWLockBase:  public  _RWLockBase
... {
       HANDLE handles[maxReadCount];
       DWORD ReadLock(
int timeout)   //加读锁,只要等到一个互斥量返回即可
       ...{
              
return ::WaitForMultipleObjects(maxReadCount, handles, FALSE, timeout);
       }

       
void ReadUnlock(int handleIndex) //解读锁,释放已获得的互斥量
       ...{
              ::ReleaseMutex(handles[handleIndex]);
       }

       DWORD WriteLock(
int timeout)   //加写锁,等到所有互斥量,从而与其他所有线程互斥
       ...{
              
return ::WaitForMultipleObjects(maxReadCount, handles, TRUE, timeout);
       }

       
void WriteUnlock()                      //解写锁,释放所有的互斥量
       ...{
              
for(int i = 0; i < maxReadCount; i++)
                     ::ReleaseMutex(handles[i]);
       }

protected:
       RWLockBase()                            
//构造函数,初始化每个互斥量
       ...{
              
for(int i = 0; i < maxReadCount; i++)
                     handles[i] 
= ::CreateMutex(0, FALSE, 0);
       }

       
~RWLockBase()                          //析构函数,销毁对象
       ...{
              
for(int i = 0; i < maxReadCount; i++)
                     ::CloseHandle(handles[i]);
       }

}
;
而相应的锁类也会稍复杂一些:
class  RWLock
... {
       
bool lockSuccess;                //因为有可能超时,需要保存是否等待成功
       int readLockHandleIndex;      //对于读锁,需要知道获得的是哪个互斥量
       _RWLockBase* _pObj;         //目标对象基类指针
public:
       
//这里通过第二个参数决定是加读锁还是写锁,第三个参数为超时的时间
       RWLock(_RWLockBase* pObj, bool readLock = trueint timeout = 3000)
       
...{
              _pObj 
= pObj;
              lockSuccess 
= FALSE;
              readLockHandleIndex 
= -1;
              
if(NULL == _pObj)
                     
return;
 
              
if(readLock)          //读锁
              ...{
                     DWORD retval 
= _pObj->ReadLock(timeout);
                     
if(retval < WAIT_ABANDONED) //返回值小于WAIT_ABANDONED表示成功
                     ...{                                               //其值减WAIT_OBJECT_0就是数组下标
                            readLockHandleIndex = retval - WAIT_OBJECT_0;
                            lockSuccess 
= TRUE;
                     }

              }

              
else
              
...{
                     DWORD retval 
= _pObj->WriteLock(timeout);
                     
if(retval < WAIT_ABANDONED) //写锁时获得了所有互斥量,无需保存下标
                            lockSuccess = TRUE;
              }

       }

       
~RWLock()
       
...{
              
if(NULL == _pObj)
                     
return;
              
if(readLockHandleIndex > -1)
                     _pObj
->ReadUnlock(readLockHandleIndex);
              
else
                     _pObj
->WriteUnlock();
       }

       
bool IsLockSuccess() const ...return lockSuccess; }
}
;
 
这样一来,读/写锁的类也就完成了,使用时与InstanceLock类似:
1 被锁对象从RWLockBase<>类继承
2 需要加读锁时,声明一个RWLock实例,并指出要加的是读锁
3 需要加写锁时,声明一个RWLock实例,并指出要加的是写锁
 
这里还是要多说两句,虽然使用纯虚函数结合模板类,使得客户端代码量减到最少,但性能上有一些影响,因为声明了虚函数,则实例中必然存在4个字节的VPTR,调用虚函数时则要查找VTABLE,空间和时间上都有微小的牺牲。而如果不使用模板类,则没有虚函数的代价,但也有牺牲:不使用模板类则需要使用动态数组,动态数组本身需要程序运行时在堆上分配,这也需要时间;指向动态数组的指针也需要占用内存,所以空间上的开锁是一样的,时间上虽然动态分配内存需要的时间应该比虚函数的调用要慢一点,但初始化只需要一次,总体来说也是值得的。所以最终要使用哪一种,就看具体需要了。
 
这里也给出一个实验。这里所用的被锁类也上一篇类似,简单的从RWLockBase类继承:
class  MyClass2:  public  RWLockBase <>
... {} ;
MyClass2 mc2;
看看两个线程函数:
// 读线程
DWORD CALLBACK ReadThreadProc(LPVOID param)
... {
       
int i = (int)param;
       RWLock 
lock(&mc2);          //加读锁
       if(lock.IsLockSuccess())             //如果加锁成功
       ...{
              Say(
"read thread %d started", i);   //为了代码短一些,假设Say函数有这种能力
              Sleep(1000);
              Say(
"read thread %d ended", i);
       }

       
else                                     //加锁超时,则显示超时信息
              Say("read thread %d timeout", i);
       
return 0;
}

// 写线程
DWORD CALLBACK WriteThreadProc(LPVOID param)
... {
       
int i = (int)param;
       RWLock 
lock(&mc2, false); //加写锁。
       if(lock.IsLockSuccess())
       
...{
              Say(
"write thread %d started", i);
              Sleep(
600);
              Say(
"write thread %d ended", i);
       }

       
else
              Say(
"write thread %d timeout", i);
       
return 0;
}
 
主线程:
        int  i;
       
for (i  =   0 ; i  <   5 ; i ++ )
              ::CreateThread(
0 0 , ReadThreadProc, (LPVOID)i,  0 0 );
       
for (i  =   0 ; i  <   5 ; i ++ )
              ::CreateThread(
0 0 , WriteThreadProc, (LPVOID)i,  0 0 );
程序共开10个线程,5个读5个写。从RWLockBase类继承时我们使用了默认的模板参数,所以最多同时允许3个读线程。程序的运行结果如下:
001 [15:07:28.484]read thread 0 started
002 [15:07:28.484]read thread 1 started
003 [15:07:28.484]read thread 2 started
004 [15:07:29.484]read thread 0 ended
005 [15:07:29.484]read thread 3 started
006 [15:07:29.484]read thread 1 ended
007 [15:07:29.484]read thread 4 started
008 [15:07:29.484]read thread 2 ended
009 [15:07:30.484]read thread 3 ended
010 [15:07:30.484]read thread 4 ended
011 [15:07:30.484]write thread 0 started
012 [15:07:31.078]write thread 0 ended
013 [15:07:31.078]write thread 1 started
014 [15:07:31.484]write thread 2 timeout
015 [15:07:31.484]write thread 3 timeout
016 [15:07:31.484]write thread 4 timeout
017 [15:07:31.687]write thread 1 ended
前三行三个读线程取得读锁,之后等一秒(第4-8行),三个读线程都结束了,并且余下的两个读线程取得读锁,虽然这时剩下了一个互斥量没有使用,但因为其他的线程都请求加写锁,写锁与其他所有线程互斥,所以还不能取得写锁。再过一秒(第9-11行),后来的两个取得读锁的线程也结束了,则第一个写线程取得写锁。600毫秒之后(第12-13行)第一个写线程结束,第二个写线程开始。400毫秒之后(第14-16行)余下的三个写线程都超时了,再后第二个写线程也结束了。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Win32环境下两种用于C++的线程同步类 的相关文章

  • 我可以使用反射更改 C# 中的私有只读字段吗?

    我想知道 由于很多事情都可以使用反射完成 我可以在构造函数完成执行后更改私有只读字段吗 注 只是好奇 public class Foo private readonly int bar public Foo int num bar num
  • Web UI 中的 .Result 出现死锁

    我正在阅读以下主题http blog stephencleary com 2012 07 dont block on async code html http blog stephencleary com 2012 07 dont bloc
  • 测试 hdf5/c++ 中的组是否存在

    我正在打开一个现有的 HDF5 文件来附加数据 我想向那个叫做的小组保证 A存在以供后续访问 我正在寻找一种简单的方法来创建 A有条件地 如果不存在则创建并返回新组 或者返回现有组 一种方法是测试 A存在 我怎样才能高效地做到这一点 根据
  • 处理 LINQ sum 表达式中的 null

    我正在使用 LINQ 查询来查找列的总和 并且在少数情况下该值有可能为空 我现在使用的查询是 int score dbContext domainmaps Where p gt p SchoolId schoolid Sum v gt v
  • 在 Windows Phone 上启动 pdf 文件时出现 System.Runtime.InteropServices.COMException

    我正在尝试使用我之前在另一个应用程序上使用过的以下工作代码打开 pdf 文件 但这一次 当流程到达此行时 我收到 System Runtime InteropServices COMException Windows System Laun
  • 我应该在单元测试中使用 AutoMapper 吗?

    我正在为 ASP NET MVC 控制器方法编写单元测试 这些控制器依赖于IMapper 我创建的用于抽象 AutoMapper 的接口 使用 Castle Windsor 通过构造函数注入传入 动作方法使用IMapper从领域对象映射到
  • 对数字进行向上和向下舍入 C++

    我试图让我的程序分别向上和向下舍入数字 例如 如果数字是3 6 我的程序应该四舍五入最接近的数字 4 如果该数字是3 4 它将向下舍入为 3 我尝试使用ceil库获取 3 个项目的平均值 results ceil marks1 marks2
  • C中有const吗?

    这个问题可能很幼稚 但是 有没有constC 中的关键字 从哪个版本开始 之间有任何语义和 或句法差异吗const在 C 和 C 中 C 和 C 之间在语法上没有差异const关键字 除了一个相当晦涩的关键字 在 C 中 自 C99 起 您
  • 成员初始值设定项列表中的求值顺序是什么?

    我有一个带有一些参数的构造函数 我假设它们是按照列出的顺序初始化的 但在一种情况下 它们似乎是按相反的顺序初始化的 导致中止 当我反转参数时 程序停止中止 下面是我正在使用的语法的示例 a 之前需要初始化b 在这种情况下 你能保证这个初始化
  • 防止复制构造和返回值引用的分配

    如果我有一个函数返回对类实例的引用 但我无法控制其源 比如说list
  • 无法实例化类对象的类型 (Java)

    这是我收到错误的代码 在 new 之后的第二个 Killer 处 String classes new String 5 kills 0 Brian Moser kills 1 James Doakes kills 2 Lila Tourn
  • 如何在不使用reinterpret_cast的情况下使用dlsym()加载函数?

    我正在尝试使用 clang tidy 来强制执行 C 核心指南 虽然它确实有很多有效点 但有一件事我无法真正解决 dlsym 返回一个void 我需要以某种方式将其转换为正确的函数指针 为此 我使用reinterpret cast 由于指南
  • _mm_max_ss 在 clang 和 gcc 之间有不同的行为

    我正在尝试使用 clang 和 gcc 交叉编译一个项目 但在使用时发现一些奇怪的差异 mm max ss e g m128 a mm set ss std numeric limits
  • 在 C# 中赋值后如何保留有关对象的信息?

    我一直在问我的想法可能是解决方案 https stackoverflow com questions 35254467 is it possible in c sharp to get the attributes attached to
  • 如何使用 Clang 查找内存泄漏

    我在我的机器 ubuntu 中安装了 Clang 以便发现我的 C 代码中的内存泄漏 我编写了一个示例代码来检查它的工作情况 如下所示 File hello c for leak detection include
  • 编写专门用于类及其子类的函数模板

    我正在尝试编写一个函数模板 一个版本应该用于不满足另一版本标准的所有类型 当参数是给定类的基类或该类本身时 应使用另一个版本 我尝试过超载Base 但是当类派生自Base 他们使用通用的 而不是特定的 我也尝试过这种 SFINAE 方法 s
  • 如何从枚举中选择随机值?

    给定 C 中的任意枚举 如何选择随机值 我没有找到这个非常基本的问题 我会在一分钟内发布我的答案作为任何人的参考 但请随意发布你自己的答案 Array values Enum GetValues typeof Bar Random rand
  • 便携式终端

    有没有办法根据所使用的操作系统自动使用正确的 EOL 字符 我在想类似的事情std eol 我知道使用预处理器指令非常容易 但很好奇它是否已经可用 我感兴趣的是 我的应用程序中通常有一些消息 稍后我会将这些消息组合成一个字符串 并且我希望将
  • 改进C++逐行读取文件的能力?

    我正在解析大约 500GB 的日志文件 我的 C 版本需要 3 5 分钟 我的 Go 版本需要 1 2 分钟 我正在使用 C 的流来流式传输文件的每一行以进行解析 include
  • 在 C# 中读取/写入命令行程序

    我正在尝试与 C 的命令行程序进行对话 它是一个情绪分析器 它的工作原理如下 CMD gt java jar analyser jar gt Starting analyser 这是我想从我的 C 程序插入内容的地方 例如 I love y

随机推荐

  • input type=“file”上传文件(一)

    使用input标签 type file 的时候就可以上传文件 为input标签添加change事件 调用函数
  • qt 添加动作 (QAction)

    Qt 5 mainwindow h ifndef MAINWINDOW H define MAINWINDOW H include
  • 3Dgame_homework7

    3D游戏 作业7 智能巡逻兵 要求 游戏设计要求 程序设计要求 提示 相关理论 实现过程与代码 智能巡逻兵 要求 游戏设计要求 创建一个地图和若干巡逻兵 使用动画 每个巡逻兵走一个 3 5 个边的凸多边型 其位置数据是相对地址 即每次确定下
  • 静态库调用动态库或者静态库(Cmake例子)

    1 静态库无论调用动态库还是静态库都只需要include库的头文件 2 要在调用该静态库的地方添加库引用 并设置路径 结论 其实静态库调用动态库或者静态库 只是在用到库方法的地方把该方法添加到LIB当中 真正使用的地方才会把这些库LINK起
  • flutter_blue优化(FlutterBlue.instance.scan搜索重复、搜索结果处理、更新之前保存缓存数据、保存连接成功的设备)

    1 搜索列表优化 FlutterBlue instance scan搜索重复 搜索结果处理 更新之前保存缓存数据 2 保存连接过的设备 3 十进制转十六进制 4 写入十六进制数据 json scan dart 实体类 主要是使用flutte
  • Spring Boot是什么?它的优点是什么?

    Spring Boot是什么 它的优点是什么 Spring Boot是一个基于Spring框架的快速开发框架 它旨在简化Spring应用程序的开发过程和部署流程 Spring Boot提供了自动化配置和约定大于配置的方式 使开发人员可以专注
  • JavaWeb--- Filter(过滤器)学习

    Filter 过滤器 处理中文乱码 登录验证 1 xml依赖
  • 教你用Python做图像处理

    质量 速度 廉价 选择其中两个 提到图像处理第一个想到的库就是PIL 全称Python Imaging Library Python 图像处理类库 它提供了大量的图像操作 比如图像缩放 裁剪 贴图 模糊等等 很多时候它需要配合numpy库一
  • 无盘服务器怎么看使用情况,无盘服务器回写盘查看

    无盘服务器回写盘查看 内容精选 换一换 华为云帮助中心 为用户提供产品简介 价格说明 购买指南 用户指南 API参考 最佳实践 常见问题 视频帮助等技术文档 帮助您快速上手使用华为云服务 由于某些机型的服务器没有配备SDI卡 或者其他服务器
  • 代码随想录一刷-Day09

    LeetCode27 移除元素 public int removeElement int nums int val if nums length 0 return 0 双指针 int slow 0 fast 0 while fast lt
  • Dynamic 356 OP 9.0版本 同服务器环境还原DB导入组织报错

    环境 Dynamic 365 9 0 6 9 PRD和UAT组织在同一套服务器环境中 操作 1 备份PRD数据库 还原到UAT数据库 2 在CRM部署管理器中导入UAT组织数据库 提示错误如下 12 31 12 Verbose Databa
  • 代码简洁3 —— 注释

    前段时间在组织代码review时 提到代码可读性问题时 很多人的第一反应竟然是多添加注释 而我始终觉得注释只能是锦上添花 而不能雪中送炭 再多的注释也改变不了代码逻辑组织混乱的现实 反而过多的注释会加重代码阅读的时间 什么也比不上放置良好的
  • 大端模式(big-endian)与小端模式(little-endian)

    参考自 大端模式与小端模式 大端模式是指数据的低位保存在内存的高地址中 而数据的高位保存在内存的低地址中 小端模式是指数据的低位保存在内存的低地址中 而数据的高位保存在内存的高地址中 大小端模式的由来 在计算机系统中 我们是以字节为单位的
  • 【Caffe】官方例程之R-CNN物体检测

    R CNN is a state of the art detector that classifies region proposals by a finetuned Caffe model For the full details of
  • kvm cpu的亲和性绑定配置

    1 CPU的绑定配置 进程的处理器亲和性 process affinity 指将进程绑定到特定的一个或多个cpu上去执行 而不允许将进程调度到其他cpu上 物理cpu 表示真实的cpu个数 逻辑cpu 表示所有拥有一个完整真实cpu功能的单
  • nodejs npm 安装报错 无法安装 behind a proxy

    npm ERR Error connect ECONNREFUSED npm ERR at errnoException net js 878 11 npm ERR at Object afterConnect as oncomplete
  • Where-are-they-looking-PyTorch 代码Error: Bool value of Tensor with more than one value is ambiguous

    在Where are they looking PyTorch中的utils py中有一个判断 if temp lt best best temp 报出 Error Bool value of Tensor with more than o
  • Puppet

    1 Puppet简介 Puppet使用一种描述性语言来定义配置项 配置项中被称为 资源 描述性语言可以声明用户的配置状态 比如声明一个软件包应该被安装或一个服务应该被启动等 Puppet可以运行在一台服务器端 每个客户端通过SSL证书连接到
  • 机器学习在工业应用中的新思考

    人工智能在学术界默默发展了很多很多年 从最早的神经网络 到10年前风靡的SVM bagging and boosting 如今的深度学习 日新月异 各领风骚数几年 工业界的发展从最早应用于纯粹的互联网企业 近几年开始应用到更多更广泛的场景
  • Win32环境下两种用于C++的线程同步类

    线程同步是多线程程序设计的核心内容 它的目的是正确处理多线程并发时的各种问题 例如线程的等待 多个线程访问同一数据时的互斥 防死锁等 Win32提供多种内核对象和手段用于线程同步 如互斥量 信号量 事件 临界区等 所不同的是 互斥量 信号量