3 回答
TA贡献1906条经验 获得超10个赞
尽管互斥锁可以用于解决其他问题,但它们存在的主要原因是提供相互排斥,从而解决了所谓的竞争条件。当两个(或多个)线程或进程试图同时访问同一变量时,我们就有竞争条件的可能性。考虑以下代码
//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()难道不是简单地增加一个变量就复杂吗?
从技术上讲,我们需要一些硬件支持来帮助我们。硬件设计师为我们提供了机器指令,这些指令不仅可以完成一件事,而且必须保证是原子的。这种指令的经典示例是测试设置(TAS)。尝试获取资源锁时,我们可能会使用TAS来检查内存中的值是否为0。如果是,则表明我们正在使用该资源,并且我们什么也不做(或更准确地说,是,我们会通过某种机制等待。pthreads互斥锁会将我们放入操作系统的特殊队列中,并在资源可用时通知我们。Dumber系统可能会要求我们执行紧密的自旋循环,一遍又一遍地测试条件) 。如果内存中的值不为0,则TAS无需使用任何其他指令即可将位置设置为0以外的值。它' 就像将两个汇编指令合并为1来赋予我们原子性。因此,一旦开始测试和更改值(如果适当的话)就不能中断。我们可以在这样的指令之上构建互斥体。
注意:某些部分可能与以前的答案类似。我接受了他的编辑邀请,他更喜欢原来的方式,所以我保留了自己的作品,并注入了一点点措辞。
TA贡献1966条经验 获得超4个赞
我最近偶然发现了这篇文章,并认为它需要标准库的c ++ 11互斥量(即std :: mutex)的更新解决方案。
我在下面粘贴了一些代码(我使用互斥锁的第一步-我在W32上通过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;
}
- 3 回答
- 0 关注
- 622 浏览
添加回答
举报