Game Engine Architecture 4

1、a model of multiple semi-independent flows of control simply matches the problem better than a single flow-of-control design.

2、There are two basic ways in which concurrent threads can communicate:

  Message passing. The messages might be sent across a network, passed between processes using a pipe, or transmitted via a message queue in memory that is accessible to both sender and receiver. This approach  

  • Shared memory.  two or more threads are granted access to the same block of physical memory, and can therefore operate directly on any data objects residing in that memory area. Threads within different processes can also share memory by mapping certain physical memory pages into all of the processes’ virtual address spaces.

3、Race Conditions

  A race condition is defined as any situation in which the behavior of a program is dependent on timing.

  竞态条件(race condition)是指设备或系统出现不恰当的执行时序,而得到不正确的结果。竞态条件(race condition),从多进程间通信的角度来讲,是指两个或多个进程对共享的数据进行读或写的操作时,最终的结果取决于这些进程的执行顺序。

4、Critical Races

  critical race is a race condition that has the potential to cause incorrect program behavior.  

  • intermittent or seemingly random bugs or crashes,
  • incorrect results,
  • data structures that get into corrupted states,
  • bugs that magically disappear when you switch to a debug build,

  • bugs that are around for a while, and then go away for a few days, onlyto return again (usually the night before E3!),

  • bugs that go away when logging (a.k.a., “printf() debugging”) is added to the program in an attempt to discover the source of the problem.

5、Data Races

  A data race is a critical race condition in which two or more flows of control intefere with one another while reading and/or writing a block of shared data, resulting in data corruption.

   Game Engine Architecture 4 

   Game Engine Architecture 4

  Upper is an example of a read-modify-write (RMW) operation.

  Data race bugs only occur when an operation on a shared object is interrupted by another operation on that same object.  

  Let’s use the term critical operation to refer to any operation that can possibly read or mutate one particular shared object. To guarantee that the shared object is free from data race bugs, we must ensure that none of its critical operations can interrupt one another

  When a critical operation is made uninterruptable in this manner, it is called an atomic operation. Alternatively, we can say that such an operation has the property of atomicity.

6、Invocation and Response

    Game Engine Architecture 4

7、Atomicity Defined

  a data race bug can occur when a critical operation is interrupted by another critical operation on the same shared object. This can happen:

    • when one thread preempts another on a single core, or

    • when two or more critical operations overlap across multiple cores.

8、define the atomicity of a critical operation as follows:

  Game Engine Architecture 4

  Game Engine Architecture 4

  Game Engine Architecture 4

9、Makeing an Operation Atomic

  how can we transform a critical operation into an atomic operation? The easiest and most reliable way to accomplish this is to use a special object called a mutex.

  the OS guarantees that a mutex can only be acquired by one thread at a time.

10、Thread Synchronization Primitives

  while these thread synchronization primitives are robust and relatively easy to use, they are generally quite expensiveThis is because these tools are provided by the kernel. Interacting with any of them therefore requires a kernel call, which involves a context switch into protected mode. Such context switches can cost upwards of 1000 clock cycles.

11、Mutex

  mutex can be in one of two states: unlocked or locked. (sometimes called released and acquired, or signaled and nonsignaled, respectively.)

  “mutex” comes from “mutual exclusion.”

  If one or more other threads is asleep (blocked) waiting on the mutex, the act of signaling it causes the kernel to select one of these waiting threads and wake it up.

12、Starting with C++11, the C++ standard library exposes kernel mutexes via the

class std::mutex.

  Game Engine Architecture 4

13、Some operating systems provide less-expensive alternatives to a mutex. For example, Microsoft Windows provides a locking mechanism known as a critical section.

  Game Engine Architecture 4

  When a thread first attempts to enter (lock) a critical section that is already locked by another thread, an inexpensive spin lock is used to wait until the other thread has left (unlocked) that critical section. A spin lock does not require a context switch into the kernel, making it a few thousand clock cycles cheaper than a mutex.

  Linux supports a thing called a “futex” that acts somewhat like a critical section under Windows.

14、condition variable (CV).

  we’d like a way to block the consumer thread (put it to sleep) while the producer does its work, and then wake it up when the data is ready to be consumed. This can be accomplished by making use of a new kind of kernel object called a condition variable (CV).

 

  In concurrent programming, we often need to send signals between threads in order to synchronize their activities.

  3) wait(). A blocking function that puts the calling thread to sleep.

  4) notify(). A non-blocking function that wakes up any threads that are currently asleep waiting on the condition variable.

  The sleep and wake operations are performed in an atomic way with the help of a mutex provided by the program, plus a little help from the kernel.

    Game Engine Architecture 4  Game Engine Architecture 4

15、Semaphores

  a semaphore acts like an atomic counter whose value is never allowed to drop below zero.

  3) take() or wait(). If the counter value encapsulated by a given semaphore is greater than zero, this function decrements the counter and returns immediately. If its counter value is currently zero, this function blocks (puts the thread to sleep) until the semaphore’s counter rises above zero again. 

  4) give(), post() or signal(). Increments the encapsulated counter value by one, thereby opening up a “slot” for another thread to take() the semaphore. If a thread is currently asleep waiting on the semaphore when give() is called, that thread will wake up from its call to take() or wait().7

  We say that a semaphore is signaled whenever its count is greater than zero, and it is nonsignaled when its counter is equal to zero.

 

  producer-consumer example, This notification mechanism can be implemented using two binary semaphores. One indicate how many item in buffer, one indicate how many room left.

Queue g_queue;
sem_t g_semUsed; // initialized to 0
sem_t g_semFree; // initialized to 1

void* ProducerThreadSem(void*)
{
    // keep on producing forever...
    while (true)
    {
        // produce an item (can be done non-
        // atomically because it's local data)
        Item item = ProduceItem();
        
        // decrement the free count
        // (wait until there's room)
        sem_wait(&g_semFree);
        
        AddItemToQueue(&g_queue, item);
        
        // increment the used count
        // (notify consumer that there's data)
        sem_post(&g_semUsed);
    }
    return nullptr;
}

void* ConsumerThreadSem(void*)
{
    // keep on consuming forever...
    while (true)
    {
        // decrement the used count
        // (wait for the data to be ready)
        sem_wait(&g_semUsed);
        Item item = RemoveItemFromQueue(&g_queue);
        
        // increment the free count
        // (notify producer that there's room)
        sem_post(&g_semFree);
        
        // consume the item (can be done non-
        // atomically because it's local data)
        ConsumeItem(item);
    }
    return nullptr;
}
View Code

相关文章:

  • 2022-02-28
  • 2021-07-09
  • 2021-08-06
  • 2021-09-25
  • 2021-12-14
  • 2021-06-27
  • 2021-08-23
  • 2021-08-12
猜你喜欢
  • 2021-07-28
  • 2021-09-16
  • 2022-02-24
  • 2021-10-24
  • 2021-10-10
  • 2021-10-10
  • 2021-08-11
相关资源
相似解决方案