当前位置: 首页 > 工具软件 > CODE2RACE > 使用案例 >

Race Conditions and Critical Sections

夏侯野
2023-12-01

A race condition is a special condition that may occur inside a critical section. A critical section is a section of code that is executed by multiple threads and where the sequence of execution for the threads makes a difference in the result of the concurrent execution of the critical section.

When the result of multiple threads executing a critical section may differ depending on the sequence in which the threads execute, the critical section is said to contain a race condition. The term race condition stems from the metaphor that the threads are racing through the critical section, and that the result of that race impacts the result of executing the critical section.

This may all sound a bit complicated, so I will elaborate more on race conditions and critical sections in the following sections.

一、Critical Sections(临界区)

Running more than one thread inside the same application does not by itself cause problems. The problems arise when multiple threads access the same resources. For instance the same memory (variables, arrays, or objects), systems (databases, web services etc.) or files.
运行多个线程不会出问题。若出问题,则是在多个线程访问相同的资源

In fact, problems only arise if one or more of the threads write to these resources. It is safe to let multiple threads read the same resources, as long as the resources do not change.
read相同的资源也不会出问题,若出问题,说明多个线程write这些资源

Race conditions occur only if multiple threads are accessing the same resource,and one or more of the threads write to the resource. If multiple threads read the same resource race conditions do not occur.

Here is a critical section Java code example that may fail if executed by multiple threads simultaneously:

  public class Counter {

     protected long count = 0;

     public void add(long value){
         this.count = this.count + value;
     }
  }

Imagine if two threads, A and B, are executing the add method on the same instance of the Counter class. **There is no way to know when the operating system switches between the two threads.**The code in the add() method is not executed as a single atomic instruction by the Java virtual machine. Rather it is executed as a set of smaller instructions, similar to this:
假设有A和B两个线程,执行add方法。我们并不能知道何时线程切换。add方法不是一个原子操作(要么全做,要么不做),而是用一系列更小的指令来执行,如下所示:

  • Read this.count from memory into register.
  • Add value to register.
  • Write register to memory.
    Observe what happens with the following mixed execution of threads A and B:
    this.count = 0;

   A:  Reads this.count into a register (0)
   B:  Reads this.count into a register (0)
   B:  Adds value 2 to register
   B:  Writes register value (2) back to memory. this.count now equals 2
   A:  Adds value 3 to register
   A:  Writes register value (3) back to memory. this.count now equals 3

The two threads wanted to add the values 2 and 3 to the counter. Thus the value should have been 5 after the two threads complete execution. However, since the execution of the two threads is interleaved(交错), the result ends up being different.
B:counter+2;A:counter+3; 因为交错执行使得A取到的counter值并不是the same instance of the Counter class的最新值,所以结果不是5.

In the execution sequence example listed above, both threads read the value 0 from memory. Then they add their individual values, 2 and 3, to the value, and write the result back to memory. Instead of 5, the value left in this.count will be the value written by the last thread to write its value. In the above case it is thread A, but it could as well have been thread B.
this.count值是A或B谁最后写入主存所对应的值。

二、Race Conditions in Critical Sections

The code in the add() method in the example earlier contains a critical section. When multiple threads execute this critical section, race conditions occur.
当多线程执行临界区时,race condition出现。

More formally, the situation where two threads compete for the same resource, where the sequence in which the resource is accessed is significant, is called race conditions. A code section that leads to race conditions is called a critical section.
当两个线程争用同一资源时,如果被访问资源的顺序很敏感,则称为竞态条件。 导致竞态条件发生的代码区称作临界区。

三、Preventing Race Conditions

To prevent race conditions from occurring you must make sure that the critical section is executed as an atomic instruction. That means that once a single thread is executing it, no other threads can execute it until the first thread has left the critical section.
避免race condition发生,就要把临界区部分的执行作为一个原子操作。也就是只有一个线程可以执行,其他线程必须等待first thread离开才能执行。

Race conditions can be avoided by proper thread synchronization in critical sections. Thread synchronization can be achieved using a synchronized block of Java code. Thread synchronization can also be achieved using other synchronization constructs like locks or atomic variables like java.util.concurrent.atomic.AtomicInteger.
Race conditions解决方法①对临界区使用synchronized代码块 ②lock ③原子变量

四、Critical Section Throughput

For smaller critical sections making the whole critical section a synchronized block may work. But, for larger critical sections it may be beneficial to break the critical section into smaller critical sections, to allow multiple threads to execute each a smaller critical section. This may decrease contention on the shared resource, and thus increase throughput of the total critical section.
对于小的临界区,可以将整个临界区放在一个synchronized block中。若临界区较大,为了提高并发,要将大临界区划分成小临界区,让小临界区并发执行,从而提高整个临界区的吞吐量。
Here is a very simplified Java code example to show what I mean:

public class TwoSums {
    
    private int sum1 = 0;
    private int sum2 = 0;
    
    public void add(int val1, int val2){
        synchronized(this){
            this.sum1 += val1;   
            this.sum2 += val2;
        }
    }
}

Notice how the add() method adds values to two different sum member variables. To prevent race conditions the summing is executed inside a Java synchronized block. With this implementation only a single thread can ever execute the summing at the same time.

However, since the two sum variables are independent of each other, you could split their summing up into two separate synchronized blocks, like this:

public class TwoSums {
    
    private int sum1 = 0;
    private int sum2 = 0;

    private Integer sum1Lock = new Integer(1);
    private Integer sum2Lock = new Integer(2);

    public void add(int val1, int val2){
        synchronized(this.sum1Lock){
            this.sum1 += val1;   
        }
        synchronized(this.sum2Lock){
            this.sum2 += val2;
        }
    }
}

Now two threads can execute the add() method at the same time. One thread inside the first synchronized block, and another thread inside the second synchronized block. The two synchronized blocks are synchronized on different objects, so two different threads can execute the two blocks independently. This way threads will have to wait less for each other to execute the add() method.

 类似资料:

相关阅读

相关文章

相关问答