互斥实例/教程?

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

互斥量的一个绝对不直观的语法是pthread_mutex_lock( &mutex1 );,它看起来像是互斥量被锁定了,而我真正想锁定的是其他变量。这种语法是否意味着锁定一个互斥锁将锁定一个代码区域,直到互斥锁被解锁?那么线程如何知道该区域是锁定的呢?[__abc1 __abc2]。这样的现象不应该被称为临界区吗?(更新:临界区对象仅在Windows中可用,其中对象比互斥对象更快,并且仅对实现它的线程可见。否则,临界区只是指互斥锁保护的代码区域)

简而言之,你能不能在它如何工作的逻辑上帮助最简单的互斥量示例程序和最简单的解释 ?我相信这将帮助其他新手<强> < / >强很多

304745 次浏览

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

互斥锁实际上只是一个简化的信号量。如果你读过并理解了它们,你就理解了互斥对象。在SO中有几个关于互斥和信号量的问题。二进制信号量和互斥量的区别什么时候应该使用互斥量,什么时候应该使用信号量等等。第一个链接中的厕所例子是人们能想到的最好的例子。所有代码所做的就是检查密钥是否可用,如果可用,则保留它。请注意,你实际上没有保留厕所本身,而是保留了钥匙。

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

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

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

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

很多人跑到一个单独的电话亭(他们没有手机)和他们所爱的人通话。谁先抓住电话亭的门把手,谁就可以使用电话。只要他使用电话,他就必须一直抓住门把手,否则其他人会抓住门把手,把他扔出去,然后和他的妻子说话:)没有这样的排队系统。当这个人打完电话,走出电话亭并留下门把手时,下一个抓住门把手的人将被允许使用电话。

A 线程是:每个人
互斥锁是门把手
是:人的手
资源是:电话

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

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

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

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;./thread编译并运行

如果你正在使用作用域锁因为它提供的优势,你可以使用括号如图所示,而不是显式地使用lockunlock。不过作用域锁有轻微的性能开销。

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

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

因为在i上执行递增操作都需要等价的汇编语言指令,所以我们说递增i是非大气操作。原子操作可以在硬件上完成,并保证在指令执行开始后不被中断。递增i由3个原子指令组成。在并发系统中,多个线程都在调用该函数,当一个线程在错误的时间读写时,就会出现问题。假设我们有两个线程同时运行,其中一个线程在另一个线程之后立即调用函数。假设i初始化为0。还要假设我们有很多寄存器,并且两个线程使用完全不同的寄存器,因此不会发生冲突。这些事件的实际时间可能是:

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()不是比变量的简单增量更复杂一点吗?

从技术上讲,我们需要一些硬件支持来帮助我们。硬件设计者给我们的机器指令不止做一件事,但保证是原子的。此类指令的一个经典示例是test-and-set (TAS)。当试图获取资源上的锁时,我们可以使用TAS检查内存中的值是否为0。如果是,这将是我们的信号,表明资源正在使用中,我们什么也不做(或者更准确地说,我们通过某种机制等待。pthreads互斥锁将把我们放到操作系统中的一个特殊队列中,并在资源可用时通知我们。更笨的系统可能需要我们做一个紧密的旋转循环,一遍又一遍地测试条件)。如果内存中的值不是0,TAS将该位置设置为0以外的值,而不使用任何其他指令。这就像将两个汇编指令合并为一个指令,从而获得原子性。因此,测试和更改值(如果更改是适当的)一旦开始就不能中断。我们可以在这样的指令之上构建互斥对象。

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

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

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

#include <mutex>


int main() {
std::mutex m;


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