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

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

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

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

5sxhfpxr

5sxhfpxr1#

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

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

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

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

使用C++11线程:

  1. #include <iostream>
  2. #include <thread>
  3. #include <mutex>
  4. std::mutex m;//you can use std::lock_guard if you want to be exception safe
  5. int i = 0;
  6. void makeACallFromPhoneBooth()
  7. {
  8. m.lock();//man gets a hold of the phone booth door and locks it. The other men wait outside
  9. //man happily talks to his wife from now....
  10. std::cout << i << " Hello Wife" << std::endl;
  11. i++;//no other thread can access variable i until m.unlock() is called
  12. //...until now, with no interruption from other men
  13. m.unlock();//man lets go of the door handle and unlocks the door
  14. }
  15. int main()
  16. {
  17. //This is the main crowd of people uninterested in making a phone call
  18. //man1 leaves the crowd to go to the phone booth
  19. std::thread man1(makeACallFromPhoneBooth);
  20. //Although man2 appears to start second, there's a good chance he might
  21. //reach the phone booth before man1
  22. std::thread man2(makeACallFromPhoneBooth);
  23. //And hey, man3 also joined the race to the booth
  24. std::thread man3(makeACallFromPhoneBooth);
  25. man1.join();//man1 finished his phone call and joins the crowd
  26. man2.join();//man2 finished his phone call and joins the crowd
  27. man3.join();//man3 finished his phone call and joins the crowd
  28. return 0;
  29. }

使用g++ -std=c++0x -pthread -o thread thread.cpp;./thread编译和运行
如果使用作用域锁for the advantage it provides,可以使用括号as shown here,而不是显式地使用lockunlock

展开查看全部
7ajki6be

7ajki6be2#

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

  1. //somewhere long ago, we have i declared as int
  2. void my_concurrently_called_function()
  3. {
  4. i++;
  5. }

这个函数的内部结构看起来很简单,只有一条语句。然而,一个典型的伪汇编语言等价物可能是:

  1. load i from memory into a register
  2. add 1 to i
  3. store i back into memory

因为在i上执行递增操作需要所有等价的汇编语言指令,所以我们说递增i是非原子操作。原子操作是指可以在硬件上完成的操作,并保证一旦指令执行开始就不会被中断。递增i由3个原子指令链组成。在多个线程调用函数的并发系统中,当一个线程在错误的时间读或写时,问题就出现了。假设我们有两个线程同时运行,一个在另一个之后立即调用函数。假设我们将i初始化为0。还假设我们有大量的寄存器,两个线程使用完全不同的寄存器,因此不会有冲突。这些事件的实际时间可能是:

  1. thread 1 load 0 into register from memory corresponding to i //register is currently 0
  2. thread 1 add 1 to a register //register is now 1, but not memory is 0
  3. thread 2 load 0 into register from memory corresponding to i
  4. thread 2 add 1 to a register //register is now 1, but not memory is 0
  5. thread 1 write register to memory //memory is now 1
  6. 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,我很乐意看到评论、建议和改进!

  1. #include <condition_variable>
  2. #include <mutex>
  3. #include <algorithm>
  4. #include <thread>
  5. #include <queue>
  6. #include <chrono>
  7. #include <iostream>
  8. int _tmain(int argc, _TCHAR* argv[])
  9. {
  10. // these vars are shared among the following threads
  11. std::queue<unsigned int> nNumbers;
  12. std::mutex mtxQueue;
  13. std::condition_variable cvQueue;
  14. bool m_bQueueLocked = false;
  15. std::mutex mtxQuit;
  16. std::condition_variable cvQuit;
  17. bool m_bQuit = false;
  18. std::thread thrQuit(
  19. [&]()
  20. {
  21. using namespace std;
  22. this_thread::sleep_for(chrono::seconds(5));
  23. // set event by setting the bool variable to true
  24. // then notifying via the condition variable
  25. m_bQuit = true;
  26. cvQuit.notify_all();
  27. }
  28. );
  29. std::thread thrProducer(
  30. [&]()
  31. {
  32. using namespace std;
  33. int nNum = 13;
  34. unique_lock<mutex> lock( mtxQuit );
  35. while ( ! m_bQuit )
  36. {
  37. while( cvQuit.wait_for( lock, chrono::milliseconds(75) ) == cv_status::timeout )
  38. {
  39. nNum = nNum + 13 / 2;
  40. unique_lock<mutex> qLock(mtxQueue);
  41. cout << "Produced: " << nNum << "\n";
  42. nNumbers.push( nNum );
  43. }
  44. }
  45. }
  46. );
  47. std::thread thrConsumer(
  48. [&]()
  49. {
  50. using namespace std;
  51. unique_lock<mutex> lock(mtxQuit);
  52. while( cvQuit.wait_for(lock, chrono::milliseconds(150)) == cv_status::timeout )
  53. {
  54. unique_lock<mutex> qLock(mtxQueue);
  55. if( nNumbers.size() > 0 )
  56. {
  57. cout << "Consumed: " << nNumbers.front() << "\n";
  58. nNumbers.pop();
  59. }
  60. }
  61. }
  62. );
  63. thrQuit.join();
  64. thrProducer.join();
  65. thrConsumer.join();
  66. return 0;
  67. }
展开查看全部
cpjpxq1n

cpjpxq1n4#

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

  1. #include <mutex>
  2. int main() {
  3. std::mutex m;
  4. m.lock();
  5. // do thread-safe stuff
  6. m.unlock();
  7. }
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#

信号示例:

  1. sem_t m;
  2. sem_init(&m, 0, 0); // initialize semaphore to 0
  3. sem_wait(&m);
  4. // critical section here
  5. sem_post(&m);

参考:http://pages.cs.wisc.edu/~remzi/Classes/537/Fall2008/Notes/threads-semaphores.txt

相关问题