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

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.

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

以下是我向世界各地的新手解释这个概念的谦卑尝试:(在我的博客上也有一个彩色编码版本)

A lot of people run to a lone phone booth (they don't have mobile phones) to talk to their loved ones. The first person to catch the door-handle of the booth, is the one who is allowed to use the phone. He has to keep holding on to the handle of the door as long as he uses the phone, otherwise someone else will catch hold of the handle, throw him out and talk to his wife :) There's no queue system as such. When the person finishes his call, comes out of the booth and leaves the door handle, the next person to get hold of the door handle will be allowed to use the phone.

一条线索是:每个人 互斥对象是:门把手 锁是:人的手 资源为:话机

任何线程都必须执行一些不应同时被其他线程修改的代码行(使用电话与他的妻子通话),必须首先获得一个互斥锁(抓住摊位的门把手)。只有这样,线程才能运行这些代码行(进行电话调用)。

一旦线程执行了该代码,它应该释放互斥锁,以便另一个线程可以获得互斥锁(其他人可以访问电话亭)。

[当考虑到现实世界的独占访问时,拥有互斥量的概念有点荒谬,但在编程世界中,我想没有其他方法可以让其他线程“看到”一个线程已经在执行一些代码行。有递归互斥锁等概念,但这个例子只是为了向您展示基本概念。希望这个例子能让你清楚地了解这个概念。

c++ 11线程:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex m;//you can use std::lock_guard if you want to be exception safe
int i = 0;

void makeACallFromPhoneBooth() 
{
    m.lock();//man gets a hold of the phone booth door and locks it. The other men wait outside
      //man happily talks to his wife from now....
      std::cout << i << " Hello Wife" << std::endl;
      i++;//no other thread can access variable i until m.unlock() is called
      //...until now, with no interruption from other men
    m.unlock();//man lets go of the door handle and unlocks the door
}

int main() 
{
    //This is the main crowd of people uninterested in making a phone call

    //man1 leaves the crowd to go to the phone booth
    std::thread man1(makeACallFromPhoneBooth);
    //Although man2 appears to start second, there's a good chance he might
    //reach the phone booth before man1
    std::thread man2(makeACallFromPhoneBooth);
    //And hey, man3 also joined the race to the booth
    std::thread man3(makeACallFromPhoneBooth);

    man1.join();//man1 finished his phone call and joins the crowd
    man2.join();//man2 finished his phone call and joins the crowd
    man3.join();//man3 finished his phone call and joins the crowd
    return 0;
}

使用g++ -std=c++0x -pthread -o thread编译并运行thread.cpp

如果您正在使用作用域锁,而不是显式地使用lock和unlock,则可以使用如下所示的括号。不过作用域锁有轻微的性能开销。

我最近偶然发现了这篇文章,并认为它需要一个标准库的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;
}

信号量示例:

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

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

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

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