我试图理解互斥是如何工作的。做了很多谷歌搜索,但它仍然留下了一些疑问,它是如何工作的,因为我创建了自己的程序,其中锁定不工作。

One absolutely non-intuitive syntax of the mutex is pthread_mutex_lock( &mutex1 );, where it looks like the mutex is being locked, when what I really want to lock is some other variable. Does this syntax mean that locking a mutex locks a region of code until the mutex is unlocked? Then how do threads know that the region is locked? [UPDATE: Threads know that the region is locked, by Memory Fencing ]. And isn't such a phenomenon supposed to be called critical section? [UPDATE: Critical section objects are available in Windows only, where the objects are faster than mutexes and are visible only to the thread which implements it. Otherwise, critical section just refers to the area of code protected by a mutex]

关于互斥锁的工作逻辑,最简单的示例程序和最简单的解释是什么?


当前回答

我最近偶然发现了这篇文章,并认为它需要一个标准库的c++11互斥锁(即std::mutex)的更新解决方案。

我在下面粘贴了一些代码(我使用互斥锁的第一步——我在win32上用HANDLE、SetEvent、WaitForMultipleObjects等学会了并发)。

因为这是我第一次尝试std::mutex和朋友,我很想看到评论,建议和改进!

#include <condition_variable>
#include <mutex>
#include <algorithm>
#include <thread>
#include <queue>
#include <chrono>
#include <iostream>


int _tmain(int argc, _TCHAR* argv[])
{   
    // these vars are shared among the following threads
    std::queue<unsigned int>    nNumbers;

    std::mutex                  mtxQueue;
    std::condition_variable     cvQueue;
    bool                        m_bQueueLocked = false;

    std::mutex                  mtxQuit;
    std::condition_variable     cvQuit;
    bool                        m_bQuit = false;


    std::thread thrQuit(
        [&]()
        {
            using namespace std;            

            this_thread::sleep_for(chrono::seconds(5));

            // set event by setting the bool variable to true
            // then notifying via the condition variable
            m_bQuit = true;
            cvQuit.notify_all();
        }
    );


    std::thread thrProducer(
        [&]()
        {
            using namespace std;

            int nNum = 13;
            unique_lock<mutex> lock( mtxQuit );

            while ( ! m_bQuit )
            {
                while( cvQuit.wait_for( lock, chrono::milliseconds(75) ) == cv_status::timeout )
                {
                    nNum = nNum + 13 / 2;

                    unique_lock<mutex> qLock(mtxQueue);
                    cout << "Produced: " << nNum << "\n";
                    nNumbers.push( nNum );
                }
            }
        }   
    );

    std::thread thrConsumer(
        [&]()
        {
            using namespace std;
            unique_lock<mutex> lock(mtxQuit);

            while( cvQuit.wait_for(lock, chrono::milliseconds(150)) == cv_status::timeout )
            {
                unique_lock<mutex> qLock(mtxQueue);
                if( nNumbers.size() > 0 )
                {
                    cout << "Consumed: " << nNumbers.front() << "\n";
                    nNumbers.pop();
                }               
            }
        }
    );

    thrQuit.join();
    thrProducer.join();
    thrConsumer.join();

    return 0;
}

其他回答

虽然互斥可以用来解决其他问题,但它们存在的主要原因是提供互斥,从而解决所谓的竞争条件。当两个(或多个)线程或进程试图同时访问同一个变量时,就有可能出现竞争条件。考虑以下代码

//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.

注意:某些部分可能与前面的答案相似。我接受了他的编辑邀请,他更喜欢最初的方式,所以我保留了我所拥有的,其中充满了他的废话。

在使用互斥锁保护的区域之前,您应该检查互斥锁变量。因此,pthread_mutex_lock()可以(取决于实现)等待mutex1被释放,或者返回一个值,表明如果其他人已经锁定了锁,则无法获得锁。

Mutex is really just a simplified semaphore. If you read about them and understand them, you understand mutexes. There are several questions regarding mutexes and semaphores in SO. Difference between binary semaphore and mutex, When should we use mutex and when should we use semaphore and so on. The toilet example in the first link is about as good an example as one can think of. All code does is to check if the key is available and if it is, reserves it. Notice that you don't really reserve the toilet itself, but the key.

对于那些寻找shortex互斥锁例子的人:

#include <mutex>

int main() {
    std::mutex m;

    m.lock();
    // do thread-safe stuff
    m.unlock();
}

函数pthread_mutex_lock()要么为调用线程获取互斥量,要么阻塞线程,直到获得互斥量。相关的pthread_mutex_unlock()释放互斥量。

把互斥锁看作一个队列;每个试图获取互斥锁的线程都将被放在队列的末尾。当一个线程释放互斥锁时,队列中的下一个线程将退出并正在运行。

临界段是指代码中可能存在不确定性的区域。这通常是因为多个线程试图访问一个共享变量。在某种同步到位之前,临界区是不安全的。互斥锁是同步的一种形式。

信号量示例:

sem_t m;
sem_init(&m, 0, 0); // initialize semaphore to 0

sem_wait(&m);
// critical section here
sem_post(&m);

参考资料:http://pages.cs.wisc.edu/~remzi/Classes/537/Fall2008/Notes/threads-semaphores.txt