Mutex示例/教程?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (14)

我对多线程很陌生,并且试图了解互斥是如何工作的。它仍然给它的工作方式留下了一些疑问,因为我创建了我自己的程序,在程序中锁不起作用。

互斥对象的一个绝对不直观的语法是pthread_mutex_lock( &mutex1 )当我真正想要锁定的是其他变量时,它看起来就像是被锁定的互斥对象。这种语法是否意味着锁定互斥锁,直到解除锁为止?那么线程如何知道该区域已被锁定

简而言之,你能帮我处理一下最简单的互斥物吗?

提问于
用户回答回答于

任何线程如果必须执行一些代码行,而这些代码不应该同时被其他线程修改,则必须首先获得互斥锁。只有这样,线程才能运行这些代码行。

一旦线程执行了该代码,它就应该释放互斥锁,以便另一个线程能够获得互斥锁。

在考虑真实世界的独占访问时,有互斥的概念有点荒谬,但在编程世界里,我想没有其他方法让其他线程“看到”线程已经在执行一些代码行了。还有递归互斥等概念,但这个示例只是为了向您展示基本概念。希望这个例子给你一个清晰的概念。

使用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

与TBB:要运行下面的程序,但是张贴TBB代码的目的是只需要看一下简单的代码就可以理解锁和解锁的顺序。

#include <iostream>
#include "/tbb/mutex.h"
#include "/tbb/tbb_thread.h"
using namespace tbb;

typedef mutex myMutex;
static myMutex sm;
int i = 0;

void someFunction() 
{ 
      //Note: Since a scoped lock is used below, you should know that you 
      //can specify a scope for the mutex using curly brackets, instead of 
      //using lock.acquire() and lock.release(). The lock will automatically 
      //get released when program control goes beyond the scope.
      myMutex::scoped_lock lock;//create a lock
      lock.acquire(sm);//Method acquire waits until it can acquire a lock on the mutex
         //***only one thread can access the lines from here...***
         ++i;//incrementing i is safe (only one thread can execute the code in this scope) because the mutex locked above protects all lines of code until the lock release.
         sleep(1);//simply creating a delay to show that no other thread can increment i until release() is executed
         std::cout<<"In someFunction "<<i<<"\n";
         //***...to here***
      lock.release();//releases the lock (duh!)      
}

int main()
{
   tbb_thread my_thread1(someFunction);//create a thread which executes 'someFunction'
   tbb_thread my_thread2(someFunction);
   tbb_thread my_thread3(someFunction);

   my_thread1.join();//This command causes the main thread (which is the 'calling-thread' in this case) to wait until thread1 completes its task.
   my_thread2.join();
   my_thread3.join();
}

请注意tbb_thread.h是不受欢迎的。

而且,而不是显式地使用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是一个非atmoic操作。原子操作是一种可以在硬件上完成的操作,保证在指令执行开始后不会被中断。递增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,我们的函数被调用了两次,但是结果与这个事实不一致。看起来这个函数只调用了一次。这是因为原子性在机器级别被“破坏”了,这意味着线程可以在错误的时间互相中断或一起工作。

我们需要一个机制来解决这个问题。我们需要对上面的指示下一些命令。一种常见的机制是阻塞除一个之外的所有线程。多线程互斥体使用此机制。

任何线程如果必须执行一些代码行,而这些代码可能同时不安全地修改其他线程的共享值(使用电话与妻子交谈),则首先应该使其获得互斥锁。这样,任何需要访问共享数据的线程都必须通过互斥锁。只有这样,线程才能执行代码。这一段代码称为关键部分。

一旦线程执行了关键部分,它就应该释放互斥锁,以便另一个线程能够获得互斥锁。

硬件设计者给我们的机器指令做了不止一件事,但却被认为是原子的。这种指令的一个典型例子是测试集(TAS)。当试图获取资源的锁时,我们可能会使用TAS检查内存中的值是否为0。如果是的话,那将是我们的信号,表明资源正在使用,我们什么也不做(或者更准确地说,我们是通过某种机制等待的。一个线程互斥体会将我们放到操作系统中的一个特殊队列中,并在资源可用时通知我们。Dumber系统可能需要我们做一个紧密的自旋回路,反复测试条件)。如果内存中的值不是0,则TAS将位置设置为0以外的位置,而不使用任何其他指令。这就像将两个组装指令合并为1,从而给我们原子性。因此,测试和更改值(如果适当的话)一旦开始就不能中断。我们可以在这种指令的基础上建立互斥。

扫码关注云+社区