Mutex与标准函数调用

时间:2016-03-15 23:15:48

标签: c++ function mutex

当我有这样的代码块时:

    mutex mtx;

    void hello(){
        mtx.lock();
        for(int i = 0; i < 10; i++){
           cout << "hello";
        }
        mtx.unlock();
    }
    void hi(){
        mtx.lock();
        for(int i = 0; i < 10; i++){
           cout << "hi";
        }
        mtx.unlock();
    }

    int main(){
       thread x(hello);
       thread y(hi);
       x.join();
       y.join();
    }

What is the difference between just calling `hello()` and `hi()`? (Like so)
   ...
   int main(){
      hello();
      hi();
   }

线程更高效吗?线程的目的是同时运行,对吧?

有人可以解释为什么我们在线程函数中使用互斥锁吗?谢谢!

4 个答案:

答案 0 :(得分:1)

  

线程的目的是同时运行,对吧?

是的,线程用于并行执行多个任务,尤其是在不同的CPU上。

  

有人可以解释为什么我们在线程函数中使用互斥量吗?

要相互序列化多个线程,例如当它们访问不可安全并发访问且需要保护的共享资源时。

答案 1 :(得分:1)

与纯串行代码相比,线程至少有两个优势。

  1. 分离逻辑上独立的指令序列的便利性。即使在单核机器上也是如此。这为您提供了逻辑并发,而不必具有并行性。

    • 拥有多个线程允许操作系统或用户级线程库在较少数量的CPU内核上复用多个逻辑线程,而无需应用程序开发人员担心其他线程和进程。
  2. 利用多核/处理器。线程允许您将执行扩展到您拥有的CPU核心数,从而实现并行性。

  3. 你的例子有点人为,因为整个线程的执行被锁定了。通常,线程独立执行许多操作,并且在访问共享资源时仅使用互斥锁。

    更具体地说,根据您的情况,您将无法获得任何表现。但是,如果您的整个线程不是互斥锁,那么您可能会获得效率。我说可能是因为运行多个线程会产生开销,这可能会抵消您获得的任何效率增益。

答案 2 :(得分:1)

  

线程更有效率吗?

没有。但请参阅最后的说明(如下)。

在单核上,线程的效率要低得多(比函数/方法调用的效率低得多)。

作为一个例子,在我的Ubuntu 15.10(64)上,使用g ++ v5.2.1,

a)使用std :: mutex强制执行的上下文切换(从一个线程到另一个线程)大约需要12,000纳秒

b)但是调用了两个简单的方法,例如std :: mutex lock()&amp; unlock(),这需要&lt; 50纳秒。 3个数量级!所以上下文切换vx函数调用没有竞争。

  

线程的目的是同时运行,对吧?

是的......但在单核处理器上不会发生这种情况。

在多核系统上,上下文切换时间仍然占主导地位。

例如,我的Ubuntu系统是双核心。我上面报告的上下文切换时间的测量使用10个线程的链,其中每个线程只是等待其输入信号量被解锁()&#39; d。当一个线程的输入信号量被解锁时,线程就会运行......但是简短的线程活动只是1)增加一个计数并检查一个标志,2)解锁()下一个线程,3) lock()自己的输入互斥,即再次等待前一个任务信号。在该测试中,我们称为main的线程使用其中一个线程的unlock()启动线程排序,并使用所有线程都可以看到的标志来停止它。

在此测量活动期间(约3秒),Linux系统监视器显示两个核心都已涉及,并报告两个核心的利用率约为60%。我预计两个核心都是100%..不知道它们为什么不是。

  

有人可以解释为什么我们在线程函数中使用互斥锁吗?谢谢   你!

我认为最常用的std :: mutex用于序列化对内存结构(可能是共享访问存储或结构)的访问。如果您的应用程序具有多个线程可访问的数据,则必须序列化每个写访问以防止竞争条件破坏数据。有时,读取和写入访问都需要序列化。 (见餐饮哲学家的问题。)

在你的代码中,作为一个例子(虽然我不知道你使用的是什么系统),但是std :: cout(一个共享结构)可能会交错&#39;文本。也就是说,线程上下文切换可能发生在打印&#34; hello&#34;或甚至是&#39;中间。这种行为通常是不受欢迎的,但可能是可以接受的。

多年前,我与vxWorks合作,我的团队学会了使用互斥锁来访问std :: cout以消除交错。这种行为可能会分散注意力,通常客户不喜欢它。 (最终,对于那个应用程序,我们取消了使用std trio-io(cout,cerr,cin))

如果您允许多于1个线程同时尝试对它们进行操作,那么各种类型的设备也可能无法正常运行。&#39;例如,在为设备应用任何其他操作之前,我已经为需要50美元或更多设备的设备编写了软件,以完成对我的软件的反应。设备在没有等待的情况下完全忽略了我的代码操作。

您还应该知道有些技术不涉及信号量,而是使用线程和IPC来提供序列化(即受保护的)资源访问。

来自维基百科,&#34;在并发编程中,监视器是一种同步构造,它允许线程同时具有互斥性,并且能够等待(阻止)某个条件变为真。&#34;

当操作系统提供合适的IPC时,我更喜欢使用Hoare监视器。在我的解释中,监视器只是一个通过IPC接受命令的线程,并且是唯一的线程来访问共享结构或设备。当只有一个线程访问结构时,需要NO互斥锁。所有其他线程必须发送消息(通过IPC)来请求(或可能命令)另一个结构更改。监视器线程一次处理一个请求,顺序退出IPC。

定义:碰撞

在&#34;线程上下文切换&#39;的上下文中和&#39;互斥信号量&#39;,&#39;碰撞&#39;当一个线程必须阻塞并等待访问资源时,就会发生这种情况,因为该资源已经在使用中了。 (即“被占领”)。这是强制上下文切换。另见术语&#34;关键部分&#34;。

当共享资源当前未使用时,不会发生冲突。 lock()和unlock()几乎没有成本(与上下文切换相比)。

当发生碰撞时,上下文切换可以减少“束”的速度。但是这一堆&#39; 可能仍然可以接受......考虑什么时候可以&#39;束缚&#39;与临界区内活动的持续时间相比较小。

最后的注释......有了这个碰撞的新想法&#39;:

a)面对许多碰撞,多线程效率会低得多。

出于意想不到的例子,功能“新”&#39;访问我们可以调用的线程共享资源&#34;动态内存&#34;。在一次体验中,每个线程在启动时生成了1000个新的线程。一个线程可以在0.5秒内完成该工作。四个线程,快速连续开始,花了40秒完成4个启动。上下文切换!

b)当您有多个核心且没有/或碰撞很少时,多个线程可以更高效。从本质上讲,如果线程很少相互作用,它们可以(大部分)同时运行。

线程效率可以是a或b之间的任何位置,当多个核心和冲突时。

例如,我的ram基于&#34; log&#34;机制似乎运行良好 - 每个日志条目一个互斥锁访问。一般来说,我故意使用最少的日志记录。在调试“发现”时挑战,我添加了额外的日志记录(可能后来删除),以确定出现了什么问题。通常,调试器优于通用日志记录技术。但有时候,添加几个日志条目效果很好。

答案 3 :(得分:0)

线程理论上同时运行,这意味着线程可以同时写入同一个内存块。例如,如果您有一个全局变量int i;,并且两个线程尝试同时写入不同的值,那么一个值仍保留在i中?

Mutex强制同步访问内存,在互斥块(mutex.lock&amp; mutex.unlock)内部保证同步内存访问并避免内存损坏。

当你调用mtx.lock()时,JUST ONE THREAD KEEPS RUNNING,并且任何其他调用相同mtx.lock()的线程都会停止,等待mtx.unlock调用。