• UE4原子操作与无锁编程


    原子操作的Interlocked函数

    // 自增操作
    int32 n1 = 100;
    // n1=n1+1; r1=n1; 
    int32 r1 = FPlatformAtomics::InterlockedIncrement(&n1); // n1=101  r1=101
    
    // 自减操作
    int32 n2 = 110;
    // n2=n2-1; r2=n2;
    int32 r2 = FPlatformAtomics::InterlockedDecrement(&n2); // n2=109  r2=109
    
    // Fetch and Add操作
    int32 n3 = 120;
    // r3=n3; n3=n3+5;
    int32 r3 = FPlatformAtomics::InterlockedAdd(&n3, 5);  // r3=120  n3=125
    
    // 数值Swap操作
    int32 n4 = 130;
    // r4=n4; n4=8;
    int32 r4 = FPlatformAtomics::InterlockedExchange(&n4, 8); // r4=130  n4=8
    
    // 指针Swap操作
    int32 n51 = 140;
    int32 n52 = 141;
    int32* pn51 = &n51;
    int32* pn52 = &n52;
    // r5=pn51;  pn51=pn52;
    void* r5 = FPlatformAtomics::InterlockedExchangePtr((void**)&pn51, pn52);  // *r5=140  *pn51=141  *pn52=141
    
    // 数值Compare and Swap操作
    int32 n61 = 150;
    int32 n62 = 151;
    int32 n63 = 150;
    // r6=n61;  if (n61==n63) {n61=n62;}
    int32 r6 = FPlatformAtomics::InterlockedCompareExchange(&n61, n62, n63);  // r6=150  n61=151  n62=151
    
    int32 n71 = 160;
    int32 n72 = 161;
    int32 n73 = 60;
    // r7=n71;  if (n71==n73) {n71=n72;}
    int32 r7 = FPlatformAtomics::InterlockedCompareExchange(&n71, n72, n73);  // r7=160  n71=160  n72=161
    
    // 指针Compare and Swap操作
    int32 n611 = 150;
    int32 n622 = 151;
    int32 n633 = 150;
    int32* pn611 = &n611;
    int32* pn622 = &n622;
    int32* pn633 = &n633;
    // r6x=pn611;  if (pn611==pn633) {pn611=pn622;}    注:比较的是指针地址
    void* r6x = FPlatformAtomics::InterlockedCompareExchangePointer((void**)&pn611, pn622, pn633); // r6x=pn611  *pn611=150  *pn622=151
    
    int32 n711 = 160;
    int32 n722 = 161;
    int32* pn711 = &n711;
    int32* pn722 = &n722;
    int32* pn733 = &n711;
    // r7x=pn711;  if (pn711==pn733) {pn711=pn722;}    注:比较的是指针地址
    void* r7x = FPlatformAtomics::InterlockedCompareExchangePointer((void**)&pn711, pn722, pn733); // r7x=pn711  pn711=pn722  *pn711=161  *pn722=161
    
    // 数值与运算
    int32 n81 = 8;
    int32 n82 = 12;
    int32 r8 = FPlatformAtomics::InterlockedAnd(&n81, n82);  //r8=8  n81=1000&1100=1000=8   n82=12
    
    // 数值或运算
    int32 n91 = 9;
    int32 n92 = 12;
    int32 r9 = FPlatformAtomics::InterlockedOr(&n91, n92);  //r9=9  n91=1001|1100=1101=13   n92=12
    
    // 数值异或运算
    int32 na1 = 9;
    int32 na2 = 12;
    int32 ra = FPlatformAtomics::InterlockedXor(&na1, na2);  // ra=9   na1=1001^1100=0101=5   na2=12

    FCriticalSection(用户模式下的临界区段)

    当有线程进入临界区段时,其他线程必须等待。基于原子操作Interlocked函数实现。

    优点:效率高(不需要昂贵的用户态切换到内核态的上下文切换)

    缺点:不能用于进程间同步,只能用于进程内各线程间同步

    平台 实现类
    Windows typedef FWindowsCriticalSection FCriticalSection;

    Mac

    Unix

    Android

    IOS

    typedef FPThreadsCriticalSection FCriticalSection;
    HoloLens typedef FHoloLensCriticalSection FCriticalSection;

    FSystemWideCriticalSection(系统范围的临界区段)

    当有线程进入临界区段时,其他线程必须等待。基于内核对象Mutex(互斥体)实现。

    优点:可用于系统范围内进程间同步,也可以用于进程内各线程间同步

    缺点:效率低(有昂贵的用户态切换到内核态的上下文切换)

    平台 实现类
    Windows typedef FWindowsSystemWideCriticalSection FSystemWideCriticalSection;
    Mac typedef FMacSystemWideCriticalSection FSystemWideCriticalSection;
    Unix typedef FUnixSystemWideCriticalSection FSystemWideCriticalSection;

    Android

    IOS

    HoloLens

    // 未实现

    typedef FSystemWideCriticalSectionNotImplemented FSystemWideCriticalSection;

    FRWLock(读写锁)

    由于读线程并不会破坏数据,因此读写锁将对锁操作的线程分成:读线程和写线程。以提高并发效率。

    读线程以共享模式(share)来获取和释放锁,写线程以独占模式(exclusive)来获取和释放锁。

    当没有写线程时,各个读线程可并发运行。写线程与写线程是互斥的;写线程与读线程也是互斥的。

    平台 实现类
    Windows typedef FWindowsRWLock FRWLock;

    Mac

    Unix

    Android

    IOS

    typedef FPThreadsRWLock FRWLock;
    HoloLens typedef FHoloLensRWLock FRWLock;

    FEvent(事件对象)

    基于操作系统内核对象实现。ue4中通过FEventFPlatformProcess::CreateSynchEvent(bool bIsManualReset)来创建;或者调用FEvent* GetSynchEventFromPool(bool bIsManualReset)从缓存池里面获取一个。

    注:bIsManualReset为TRUE表示为手动重置事件;为FALSE表示为自动重置事件。操作系统将所有等待该Event线程切换到就绪后,会自动调用Reset将Event设置成未触发状态。因此,开发者自己不需要显示地调用Reset。

    执行Trigger函数,可将Event设置成触发状态;执行Reset函数,可将Event设置成未触发状态。

    调用Wait函数来等待一个Event。注:Event处于未触发状态时,会阻塞等待。

    平台 实现类
    Windows

    FEventWin : public FEvent

    Mac

    Unix

    Android

    IOS

    FPThreadEvent : public FEvent

    HoloLens FEventHoloLens : public FEvent

    FSemaphore(信号量)

    仅在windows平台上实现,详见:FWindowsSemaphore

    建议使用FEvent来替代

    线程安全(Threadsafe)的容器

    包括TArray,、TMapTSet在内的容器都不是线程安全的,需要自己对同步进行管理。

    类型 解释说明
    TArrayWithThreadsafeAdd

    TArray上派生

    提供了AddThreadsafe函数来线程安全地往数组中添加元素

    注1:不会引发扩容时,AddThreadsafe才线程安全

    注2:其他的操作(AddRemoveEmpty等)都不是线程安全的

    TLockFreePointerListFIFO

    无锁队列(lock free queue),先进先出(FIFO)

    基类FLockFreePointerFIFOBase

    template<class T, int TPaddingForCacheContention, uint64 TABAInc = 1> // TABAInc为解决无锁编程中ABA问题添加的参数值
    class FLockFreePointerFIFOBase
    {
      // 内存空隙(pad),防止cpu读内存的高速缓存行(Cache Line)机制引发伪共享(False sharing)问题,导致急剧地性能下降
      FPaddingForCacheContention<TPaddingForCacheContention> PadToAvoidContention1;
      TDoublePtr Head; // 头指针 被pad包裹
      FPaddingForCacheContention<TPaddingForCacheContention> PadToAvoidContention2;
      TDoublePtr Tail; // 尾指针 被pad包裹
      FPaddingForCacheContention<TPaddingForCacheContention> PadToAvoidContention3;
    };

    TLockFreePointerListUnordered

    无锁栈(lock free stack),后进先出(LIFO)

    基类FLockFreePointerListLIFOBase

    template<class T, int TPaddingForCacheContention, uint64 TABAInc = 1> // TABAInc为解决无锁编程中ABA问题添加的参数值
    class FLockFreePointerListLIFOBase
    {
      FLockFreePointerListLIFORoot<TPaddingForCacheContention, TABAInc> RootList;
      {
        // 内存空隙(pad),防止cpu读内存的高速缓存行(Cache Line)机制引发伪共享(False sharing)问题,导致急剧地性能下降
        FPaddingForCacheContention<TPaddingForCacheContention> PadToAvoidContention1;
        TDoublePtr Head; // 栈指针 被pad包裹
        FPaddingForCacheContention<TPaddingForCacheContention> PadToAvoidContention2;
      };
    };

    TLockFreePointerListLIFO

    内存空隙为0的无锁栈,后进先出(LIFO)

    等价于TLockFreePointerListUnordered<T,TPaddingForCacheContention=0>

    FStallingTaskQueue

    无锁任务队列

    线程在while循环里不断的从队列里取任务然后执行,如果队列是空的,就会空跑while循环。虽然循环没有多少逻辑,也是会耗费系统资源的

    比较好的办法就是让线程在没有任务执行时Wait等待,当把任务加进队列之后再Trigger唤醒线程,继续while循环

    FStallingTaskQueue就是用来作此优化的类,stalling就是搁置线程的意思。另外它包含两个FLockFreePointerFIFOBase无锁队列,作为高优先级和低优先级队列

    虽然真正让线程Wait等待和Trigger唤醒的地方并不是在这个类里实现的,但它维护了各个线程的搁置状态

    这些状态记录在一个TDoublePtr MasterState里,还是这个64位指针,不过这次它的低26位不是指针了,而是表示26个线程的搁置状态,1表示被搁置,可以被唤醒

    高于26位的部分仍然作为标记计数,防止ABA问题

    TQueue

    基于链表(linked list)实现的不能插队(non-intrusive)的无锁队列(lock free queue),先进先出(FIFO)

    有两种模式(EQueueMode):Mpsc(多生产者单消费者)和Spsc(单生产者单消费者)。

    其中Spsc模式是无竞争(contention free)的。

    从Head处入队(Enqueue),从Tail处出队(Dequeue)。示意图如下:

       Tail                            Head
        |                               |
        V                               V
    | Node C | --> | Node B | --> |  Node A | --> | nullptr |
    TCircularQueue

    基于数组(TArray)实现的循环无锁队列,先进先出(FIFO)

    在仅有一个生产者和一个消费者时,线程安全

    线程安全(Threadsafe)的Helper工具类

    类型 解释说明
    FThreadSafeCounter

    基于原子操作的FPlatformAtomics::Interlocked函数实现的线程安全的int32计数器

    如:FThreadSafeCounter OutstandingHeartbeats;

    FThreadSafeCounter64 基于原子操作的FPlatformAtomics::Interlocked函数实现的线程安全的int64计数器
    FThreadSafeBool FThreadSafeCounter上派生实现的线程安全的Bool
    TThreadSingleton 为每一个线程创建单例
    FThreadIdleStats TThreadSingleton派生,用于计算各个线程的等待时间的统计逻辑
    FMemStack TThreadSingleton派生,基于每个线程进行内存分配
    TLockFreeFixedSizeAllocator 固定大小的lockfree的内存分配器
    TLockFreeClassAllocator TLockFreeFixedSizeAllocator派生的对象内存分配器
    FScopeLock

    基于作用域的自旋锁

    利用c++的RAIIResource Acquisition is Initialization)特性(即:对象构造的时候其所需的资源便应该在构造函数中初始化,而对象析构的时候则释放这些资源),基于FCriticalSection实现的自旋锁(或旋转锁)。

    {
        // CritSection为一个FCriticalSection临界区段对象
        FScopeLock ScopeLock(&CritSection); // 如果CritSection被其他线程持有,则当前线程会在此等待

        // 临界段(critical section)


        // 离开作用域,ScopeLock对象生命周期结束,会在其析构函数中释放对CritSection临界区段对象的占用
    }

    FScopedEvent

    基于作用域的同步事件

    利用c++的RAII(Resource Acquisition is Initialization)特性,基于FEvent实现的同步等待事件。

    {
        FScopedEvent MyEvent;
        SendReferenceOrPointerToSomeOtherThread(&MyEvent); // 当其他线程中完成了任务时,调用MyEvent->Trigger(),将MyEvent变成触发态

       // 离开作用域,会调用析构函数。MyEvent在析构函数中Wait任务的完成
    }

    参考

    UE4异步操作总结

    Concurrency & Parallelism in UE4 

    【UE4源代码观察】学习队列模板TQueue 

    UE4的多线程——无锁LockFree

    UE并发-生产者消费者模式

  • 相关阅读:
    npm run build无法打包的可能原因 npm ERR! missing script: build
    java的Scanner类的close()方法引来的故事
    Markdown语法1
    MarkdownPad2破解安装&使用
    MarkDown语法5
    Markdown语法2
    Markdown0
    Sublime Text配置anaconda环境
    解决:Tensorflowgpu中的Could not load dynamic library ‘cudart64_101.dll‘; dlerror: cudart64_101.dll not found
    Markdown语法4
  • 原文地址:https://www.cnblogs.com/kekec/p/13750892.html
Copyright © 2020-2023  润新知