为了账号安全,请及时绑定邮箱和手机立即绑定

Mutex示例/教程?

Mutex示例/教程?

C++ C
慕少森 2019-10-14 10:22:59
我是多线程的新手,并试图了解互斥锁的工作原理。做了很多谷歌搜索,我发现了一个不错的教程,但是它仍然对它的工作方式产生了一些疑问,因为我创建了自己的程序,其中锁不起作用。互斥量的一种绝对不直观的语法pthread_mutex_lock( &mutex1 );是,当我真正想要锁定的是其他变量时,它看起来像互斥量被锁定了。这种语法是否意味着锁定互斥锁会锁定代码区域,直到互斥锁解锁为止?那么线程如何知道该区域已锁定?[ 更新:线程知道该区域已被 Memory Fencing 锁定 ]。难道这种现象不应该称为临界区吗?[ 更新:关键部分对象仅在Windows中可用,其中这些对象比互斥对象快,并且仅对实现该对象的线程可见。否则,关键部分仅指由互斥体保护的代码区域 ]简而言之,能否请您提供最简单的互斥体示例程序以及有关其工作原理的最简单的解释?我相信这将对其他许多新手有所帮助。
查看完整描述

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来赋予我们原子性。因此,一旦开始测试和更改值(如果适当的话)就不能中断。我们可以在这样的指令之上构建互斥体。


注意:某些部分可能与以前的答案类似。我接受了他的编辑邀请,他更喜欢原来的方式,所以我保留了自己的作品,并注入了一点点措辞。


查看完整回答
反对 回复 2019-10-14
?
慕标5832272

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;

}


查看完整回答
反对 回复 2019-10-14
  • 3 回答
  • 0 关注
  • 595 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信