c++ 互斥锁示例/教程?[已关闭]

lstz6jyr  于 2022-12-30  发布在  其他
关注(0)|答案(7)|浏览(117)

**已关闭。**此问题不符合Stack Overflow guidelines。当前不接受答案。

我们不允许问题寻求有关书籍、工具、软件库等的推荐。你可以编辑问题,以便可以使用事实和引用来回答问题。
两年前关闭了。
Improve this question
我试着去理解互斥锁是如何工作的,我搜索了很多,但仍然留下了一些疑问,因为我创建了自己的程序,在其中锁定不起作用。
互斥锁的一个绝对不直观的语法是pthread_mutex_lock( &mutex1 );,它看起来像是互斥锁被锁定了,而我真正想锁定的是其他变量。这个语法是否意味着锁定互斥锁会锁定代码区域,直到互斥锁被解锁?那么线程如何知道该区域被锁定了呢?[*UPDATE:线程知道这个区域被 *Memory Fencing锁定了]。这种现象不应该被称为临界区吗?[*UPDATE:临界区对象仅在Windows中可用,在Windows中,临界区对象比互斥锁快,并且仅对实现它的线程可见。否则,临界区仅指受互斥锁保护的代码区域 *]
最简单的互斥量示例程序是什么?关于它如何工作的逻辑,最简单的解释是什么?

5sxhfpxr

5sxhfpxr1#

  • 以下是我谦卑的尝试,向全世界的新手解释这个概念:(我博客上也有一个color coded version)*

很多人跑到一个单独的电话亭(他们没有移动的)和他们的亲人说话。第一个抓住电话亭门把手的人,是被允许使用电话的人。只要他在使用电话,他就必须一直抓住门把手,否则别人会抓住把手,把他扔出去和他的妻子说话:)没有排队系统。当这个人打完电话,走出电话亭,离开门把手,下一个抓住门把手的人将被允许使用电话。

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

任何一个线程,如果要执行一些不应该被其他线程同时修改的代码行(比如打电话给妻子),必须首先获得互斥锁(比如抓住电话亭的门把手),只有这样线程才能运行这些代码行(比如打电话)。
一旦线程执行了该代码,它就应该释放互斥锁,以便另一个线程可以获取互斥锁(其他人可以访问电话亭)。
[* 在考虑现实世界的独占访问时,拥有互斥锁的概念有点荒谬,但在编程世界中,我猜没有其他方法可以让其他线程“看到”某个线程已经在执行某些代码行。有递归互斥锁等概念,但这个示例只是为了向您展示基本概念。希望这个示例能让您清楚地了解这个概念。*]

使用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编译和运行
如果使用作用域锁for the advantage it provides,可以使用括号as shown here,而不是显式地使用lockunlock

7ajki6be

7ajki6be2#

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

//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互斥体使用这种机制。
必须执行某些代码行的任何线程,这些代码行可能会同时不安全地修改其他线程的共享值(使用电话与他的妻子交谈),应该首先获得互斥锁上的锁。以这种方式,任何需要访问共享数据的线程都必须通过互斥锁。2只有这样,线程才能执行代码。3这段代码叫做临界区。
一旦线程执行了临界区,它就应该释放互斥锁,以便另一个线程可以获取互斥锁。
考虑到人类寻求对真实的物理对象的独占访问,互斥锁的概念似乎有点奇怪,但在编程时,我们必须是有意的。并发线程和进程没有我们所拥有的社会和文化背景,所以我们必须迫使它们很好地共享数据。
那么从技术上讲,互斥锁是如何工作的呢?它是否也会遇到我们前面提到的竞争条件呢?pthread_mutex_lock()是否比简单的变量增量更复杂一些呢?
从技术上讲,我们需要一些硬件支持来帮助我们。硬件设计师给予我们机器指令,这些指令可以做不止一件事,但保证是原子的。这种指令的一个经典例子是测试和设置当试图获取对资源的锁定时,我们可以使用TAS来检查以查看存储器中的值是否为0。这将是我们的信号,资源正在使用中,我们什么也不做(或者更准确地说,我们通过某种机制等待。一个pthreads互斥体会把我们放进操作系统中的一个特殊队列中,当资源变得可用时会通知我们。Dumber系统可能会要求我们做一个紧密的自旋循环,反复测试条件)。如果存储器中的值不为0,则TAS将位置设置为0以外的值,而不使用任何其他指令。这就像将两个汇编指令组合为1以给予我们原子性。因此,测试和改变值(如果改变是适当的)一旦开始就不能被中断。2我们可以在这样的指令上建立互斥锁。
注:有些部分可能看起来和之前的回答相似,我接受了他的编辑邀请,他更喜欢原来的方式,所以我保留了我的东西,其中注入了一点他的赘言。

aoyhnmkz

aoyhnmkz3#

我最近偶然发现了这篇文章,认为它需要一个针对标准库c++11互斥锁(即std::mutex)的更新解决方案。
我已经粘贴了一些代码如下(我的第一步与互斥锁-我学习了并发在win32与HANDLE,SetEvent,等待多个对象等)。
因为这是我第一次尝试使用std::mutex和friends,我很乐意看到评论、建议和改进!

#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;
}
cpjpxq1n

cpjpxq1n4#

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

#include <mutex>

int main() {
    std::mutex m;

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

8ehkhllq5#

函数pthread_mutex_lock()要么为调用线程 * 获取 * 互斥锁,要么阻塞线程直到获取互斥锁。相关的pthread_mutex_unlock()释放互斥锁。
可以将互斥体看作一个队列;每一个试图获取互斥锁的线程都将被放在队列的末尾。2当一个线程释放互斥锁时,队列中的下一个线程将退出并开始运行。
临界区指的是可能存在非确定性的代码区域。这通常是因为多个线程试图访问一个共享变量。临界区是不安全的,直到某种同步到位。互斥锁是同步的一种形式。

uhry853o

uhry853o6#

在使用互斥锁保护的区域之前,应该检查互斥锁变量,所以pthread_mutex_lock()可以(取决于实现)等到mutex1被释放,或者返回一个值,表明如果其他人已经锁定了锁,则无法获得该锁。
互斥量实际上只是一个简化的信号量。如果你读过关于互斥量的文章,并且理解了它们,你就理解了互斥量。在SO.Difference between binary semaphore and mutexWhen should we use mutex and when should we use semaphore等中有几个关于互斥量和信号量的问题。第一个链接中的马桶例子是一个很好的例子。所有代码所做的就是检查密钥是否可用,如果可用,请注意,您实际上并没有保留马桶本身,而是保留了钥匙。

w1e3prcc

w1e3prcc7#

信号示例:

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

相关问题