线程冲突和竞争条件

线程使程序能够同时执行多个进程。通常当程序使用线程时,线程将处理共享数据。当多个线程试图同时处理同一段数据时,线程之间就有可能发生冲突。在这些注释中,我将解释线程是如何发生冲突的,并介绍一种常用的调节机制,程序可以使用它来防止线程冲突。

一个例子

下面是一个简单程序的代码,它说明了线程冲突的可能性。

#include <stdio.h> #include <pthread.h> void* count(void* arg) {int* counter = (int*) arg;For (int n = 0;n < 10000;n++) (*counter)++;} int main() {pthread_t t1, t2;Int counter = 0;pthread_create (t1, NULL,计数,计数器);pthread_create (t2, NULL,计数,计数器);pthread_join (t1、空);pthread_join (t2, NULL);printf(“计数器是%d\n”,计数器);返回0;}

程序启动两个线程,每个线程都使用相同的线程函数。该线程函数尝试将共享计数器增加10000次。我们像往常一样通过调用pthread_create()启动线程,然后为每个线程调用pthread_join()。pthread_join()函数将导致main()等待,直到给定的线程完成运行。这样,在我们尝试打印计数器变量的值之前,我们给两个线程一个机会来完成它们的工作。

在这个程序中,我们有一个共享资源,即main()中的计数器变量。然后创建两个线程,它们的线程函数都将接收一个指向该共享变量的指针。这两个线程都运行一个循环,试图将计数器增加10000倍。在两个线程都停止运行之后,我们期望计数器变量的最终值为20000。相反,计数器变量的最终结果几乎永远不会是20000。每次运行程序时,最终值都会发生变化,并且最终值几乎永远不会是20000的正确值。

这是怎么回事?首先要理解的是,两个线程运行相同的代码,并且两个线程函数都有指向相同计数器变量的指针。当两个函数试图同时操作计数器变量时,可能会出现冲突。代码中发生这种情况的唯一地方是在对计数器进行加1的语句中。

这里发生的部分原因是,增加计数器的语句似乎是一个可以在单个步骤中完成的原子操作。实际上,即使是简单的增量操作也不是CPU可以作为单个步骤执行的操作。为了增加计数器变量,CPU必须将计数器变量的当前值移动到寄存器,将该寄存器增加1,然后将新值移动回计数器变量。看似一个步骤,实际上是三个步骤。一旦一个操作需要一条以上的指令才能完成,我们就有可能出现所谓的竞争条件。在竞争条件下,两个或多个线程试图完成相同的指令序列。线程必须面对的一个事实是,操作系统经常在任意点停止线程,并将它们放在一边,以便给其他线程一个运行的机会。如果其中一个试图增加计数器的线程在增加的过程中中途中断,并且在很晚之后才完成增量,这可能导致该线程将错误的值放回计数器。

通过使用互斥锁使操作原子化

争用条件和许多其他线程冲突的解决方案是使用额外的调节机制来防止多个线程同时访问代码的关键部分。

下面是演示如何做到这一点的程序的第二个版本:

#include <stdio.h> #include <pthread.h> static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;Void * count(Void * arg) {int* counter = (int*) arg;For (int n = 0;n < 10000;n++) {pthread_mutex_lock(&mutex);(*柜台)+ +;pthread_mutex_unlock(互斥);}} int main() {pthread_t t1, t2;Int counter = 0;pthread_create (t1, NULL,计数,计数器);pthread_create (t2, NULL,计数,计数器);pthread_join (t1、空);pthread_join (t2, NULL);printf(“计数器是%d\n”,计数器);返回0;}

这里的解决方案是引入互斥锁。术语“互斥”是“互斥”的缩写。互斥锁允许一个线程锁定一段代码,这样只要该线程在受保护的段内,其他线程就无法进入。

这里的代码展示了如何初始化互斥锁。由于互斥锁需要由多个线程共享,因此我们将互斥锁变量声明为静态类型,这有效地使其成为赢博体育线程都可以看到和使用的共享全局资源。

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

要使用互斥锁,线程首先通过调用pthread_mutex_lock()锁定互斥锁。锁定互斥锁会影响其他线程对该函数的调用。如果一个线程调用pthread_mutex_lock(),其他线程对该函数的任何后续调用都将阻塞,导致其他线程在该位置停止运行,直到情况发生变化。在第一个线程完成它的工作后,它将调用pthread_mutex_unlock()来解锁互斥锁,并离开受保护的代码段。一旦互斥锁被解锁,操作系统就会选择一个当前在pthread_mutex_lock()调用中被阻塞的线程,并允许它进行处理。当第二个线程在临界区运行时,又会再次锁定互斥锁。

这种调节机制解决了原有竞态条件所带来的问题。这个版本的程序将总是做正确的事情。当程序打印计数器的最终值时,它总是正确的值20000。