虽然互斥可以用来解决其他问题,但它们存在的主要原因是提供互斥,从而解决所谓的竞争条件。当两个(或多个)线程或进程试图同时访问同一个变量时,就有可能出现竞争条件。考虑以下代码
//somewhere long ago, we have i declared as int
void my_concurrently_called_function()
{
i++;
}
这个函数的内部结构看起来很简单。这只是一个表述。然而,一个典型的伪汇编语言的对等物可能是:
load i from memory into a register
add 1 to i
store i back into memory
Because the equivalent assembly-language instructions are all required to perform the increment operation on i, we say that incrementing i is a non-atmoic operation. An atomic operation is one that can be completed on the hardware with a gurantee of not being interrupted once the instruction execution has begun. Incrementing i consists of a chain of 3 atomic instructions. In a concurrent system where several threads are calling the function, problems arise when a thread reads or writes at the wrong time. Imagine we have two threads running simultaneoulsy and one calls the function immediately after the other. Let's also say that we have i initialized to 0. Also assume that we have plenty of registers and that the two threads are using completely different registers, so there will be no collisions. The actual timing of these events may be:
thread 1 load 0 into register from memory corresponding to i //register is currently 0
thread 1 add 1 to a register //register is now 1, but not memory is 0
thread 2 load 0 into register from memory corresponding to i
thread 2 add 1 to a register //register is now 1, but not memory is 0
thread 1 write register to memory //memory is now 1
thread 2 write register to memory //memory is now 1
发生的事情是,我们有两个线程并发递增i,我们的函数被调用了两次,但结果与事实不一致。看起来这个函数只被调用了一次。这是因为原子性在机器级别上被“破坏”了,这意味着线程可能会相互中断或在错误的时间一起工作。
我们需要一种机制来解决这个问题。我们需要对上面的指示进行一些排序。一种常见的机制是阻塞除一个线程之外的所有线程。Pthread互斥就使用了这种机制。
任何线程都必须执行一些代码行,而这些代码行可能会不安全地同时被其他线程修改共享值(使用电话与他的妻子通话),应该首先获得一个互斥锁。这样,任何需要访问共享数据的线程都必须通过互斥锁。只有这样,线程才能执行代码。这段代码称为临界段。
一旦线程执行了临界区,它应该释放互斥锁,以便另一个线程可以获得互斥锁。
当考虑到人们寻求对真实的物理对象的独占访问时,拥有互斥的概念似乎有点奇怪,但在编程时,我们必须有意为之。并发线程和进程没有像我们这样的社会和文化背景,所以我们必须迫使它们很好地共享数据。
那么从技术上讲,互斥是如何工作的呢?它是否会遭受我们之前提到的相同的竞争条件?pthread_mutex_lock()不是比变量的简单增量更复杂一点吗?
Technically speaking, we need some hardware support to help us out. The hardware designers give us machine instructions that do more than one thing but are guranteed to be atomic. A classic example of such an instruction is the test-and-set (TAS). When trying to acquire a lock on a resource, we might use the TAS might check to see if a value in memory is 0. If it is, that would be our signal that the resource is in use and we do nothing (or more accurately, we wait by some mechanism. A pthreads mutex will put us into a special queue in the operating system and will notify us when the resource becomes available. Dumber systems may require us to do a tight spin loop, testing the condition over and over). If the value in memory is not 0, the TAS sets the location to something other than 0 without using any other instructions. It's like combining two assembly instructions into 1 to give us atomicity. Thus, testing and changing the value (if changing is appropriate) cannot be interrupted once it has begun. We can build mutexes on top of such an instruction.
注意:某些部分可能与前面的答案相似。我接受了他的编辑邀请,他更喜欢最初的方式,所以我保留了我所拥有的,其中充满了他的废话。