当多个控制线程共享相同的内存时,需要确保每个线程看到一致的数据视图。如果每个线程使用的变量都是其他线程不会读取和修改的,那么就不存在一致性问题。同样,如果变量是只读的,多个线程同时读取该变量也不会有一致性问题。但是,当一个线程可以修改的变量,其他线程也可以读取或者修改的时候,我们就需要对这些线程进行同步,确保它们在访问变量的存储内容时不会访问到无效的值。

当一个线程修改变量时,其他线程在读取这个变量时可能会看到一个不一致的值。在变量修改时间多于一个存储器访问周期的处理器结构中,当存储器读与存储器写这两个周期交叉时,这种不一致就会出现。当然,这种行为是与处理器体系结构相关的,但是可移植的程序并不能对使用何种处理器体系结构做出任何假设。

图 11-7 描述了两个线程读写相同变量的假设例子。在这个例子中,线程 A读取变量然后给这个变量赋予一个新的数值,但写操作需要两个存储器周期。当线程B在这两个存储器写周期中间读取这个变量时,它就会得到不一致的值。

为了解决这个问题,线程不得不使用锁,同一时间只允许一个线程访问该变量。图11-8描述了这种同步。如果线程B希望读取变量,它首先要获取锁。同样,当线程A更新变量时,也需要获取同样的这把锁。这样,线程B在线程A释放锁以前就不能读取变量。

图11-7 两个线程的交叉存储器周期

图11-8 两个线程同步内存访问

两个或多个线程试图在同一时间修改同一变量时,也需要进行同步。考虑变量增量操作的情况(图11-9),增量操作通常分解为以下3步。

  1. 从内存单元读入寄存器。
  2. 在寄存器中对变量做增量操作。
  3. 把新的值写回内存单元。

如果两个线程试图几乎在同一时间对同一个变量做增量操作而不进行同步的话,结果就可能出现不一致,变量可能比原来增加了1,也有可能比原来增加了2,具体增加了1还是2要取决于第二个线程开始操作时获取的数值。如果第二个线程执行第1步要比第一个线程执行第3步要早,第二个线程读到的值与第一个线程一样,为变量加1,然后写回去,事实上没有实际的效果,总的来说变量只增加了1。

如果修改操作是原子操作,那么就不存在竞争。在前面的例子中,如果增加1只需要一个存储器周期,那么就没有竞争存在。如果数据总是以顺序一致出现的,就不需要额外的同步。当多个线程观察不到数据的不一致时,那么操作就是顺序一致的。在现代计算机系统中,存储访问需要多个总线周期,多处理器的总线周期通常在多个处理器上是交叉的,所以我们并不能保证数据是顺序一致的。

图11-9 两个非同步的线程对同一个变量做增量操作

在顺序一致环境中,可以把数据修改操作解释为运行线程的顺序操作步骤。可以把这样的操作描述为“线程A对变量增加了1,然后线程B对变量增加了1,所以变量的值就比原来的大2”,或者描述为“线程B对变量增加了1,然后线程A对变量增加了1,所以变量的值就比原来的大2”。这两个线程的任何操作顺序都不可能让变量出现除了上述值以外的其他值。

除了计算机体系结构以外,程序使用变量的方式也会引起竞争,也会导致不一致的情况发生。例如,我们可能对某个变量加1,然后基于这个值做出某种决定。因为这个增量操作步骤和这个决定步骤的组合并非原子操作,所以就给不一致情况的出现提供了可能。

results matching ""

    No results matching ""