C++ 互斥体示例/教程?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4989451/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Mutex example / tutorial?
提问by Nav
I'm new to multithreading, and was trying to understand how mutexes work. Did a lot of Googling and I found a decent tutorial, but it still left some doubts of how it works because I created my own program in which locking didn't work.
我是多线程的新手,并试图了解互斥锁的工作原理。做了很多谷歌搜索,我找到了一个不错的教程,但它仍然对它的工作原理有一些疑问,因为我创建了自己的程序,其中锁定不起作用。
One absolutely non-intuitive syntax of the mutex is pthread_mutex_lock( &mutex1 );
, where it looks like the mutex is being locked, when what I really want to lock is some other variable. Does this syntax mean that locking a mutex locks a region of code until the mutex is unlocked? Then how do threads know that the region is locked? [UPDATE: Threads know that the region is locked, byMemory Fencing]. And isn't such a phenomenon supposed to be called critical section? [UPDATE: Critical section objects are available in Windows only, where the objects are faster than mutexes and are visible only to the thread which implements it. Otherwise, critical section just refers to the area of code protected by a mutex]
互斥锁的一种绝对不直观的语法是pthread_mutex_lock( &mutex1 );
,看起来互斥锁被锁定了,而我真正想要锁定的是某个其他变量。这个语法是否意味着锁定互斥锁会锁定一个代码区域,直到互斥锁被解锁?那么线程如何知道该区域被锁定了呢?[更新:线程知道该区域已被内存防护锁定]。而且这种现象不应该叫做临界区吗?[更新:临界区对象仅在 Windows 中可用,其中对象比互斥锁更快,并且仅对实现它的线程可见。否则,临界区仅指受互斥锁保护的代码区域]
In short, could you please help with the simplest possible mutex example programand the simplest possible explanationon the logic of how it works? I'm sure this will help plentyof other newbies.
简而言之,您能否帮助提供最简单的互斥体示例程序以及对其工作原理的最简单的解释?我相信这会帮助很多其他新手。
回答by Nav
Here goes my humble attempt to explain the concept to newbies around the world: (a color coded versionon my blog too)
这是我向世界各地的新手解释这个概念的谦虚尝试:(我博客上的彩色编码版本也是如此)
A lot of people run to a lone phone booth (no mobiles) to talk to their loved ones. The first person to catch the door-handle of the booth, is the one who is allowed to use the phone. He has to keep holding on to the handle of the door as long as he uses the phone, otherwise someone else will catch hold of the handle, throw him out and talk to his wife :) There's no queue system as such. When the person finishes his call, comes out of the booth and leaves the door handle, the next person to get hold of the door handle will be allowed to use the phone.
许多人跑到一个单独的电话亭(没有手机)与他们的亲人交谈。第一个抓住展位门把手的人,就是被允许使用电话的人。只要他使用电话,他就必须一直握着门把手,否则其他人会抓住把手,把他扔出去和他的妻子说话:) 没有这样的排队系统。当此人结束通话,走出电话亭并离开门把手时,下一个抓住门把手的人将被允许使用电话。
A threadis : Each person
The mutexis : The door handle
The lockis : The person's hand
The resourceis : The phone
一个线程是:每个人
的互斥是:门把手
的锁是:人的手
的资源是:手机
Any thread which has to execute some lines of code which should not be modified by other threads at the same time (using the phone to talk to his wife), has to first acquire a lock on a mutex (clutching the door handle of the booth). Only then will a thread be able to run those lines of code (making the phone call).
任何线程必须执行一些不应被其他线程同时修改的代码行(使用电话与妻子通话),必须首先获得互斥锁(抓住展位的门把手) )。只有这样,线程才能运行这些代码行(拨打电话)。
Once the thread has executed that code, it should release the lock on the mutex so that another thread can acquire a lock on the mutex (other people being able to access the phone booth).
一旦线程执行了该代码,它应该释放互斥锁上的锁,以便另一个线程可以获得互斥锁上的锁(其他人可以访问电话亭)。
[The concept of having a mutex is a bit absurd when considering real-world exclusive access, but in the programming world I guess there was no other way to let the other threads 'see' that a thread was already executing some lines of code. There are concepts of recursive mutexes etc, but this example was only meant to show you the basic concept. Hope the example gives you a clear picture of the concept.]
[在考虑现实世界的独占访问时,拥有互斥锁的概念有点荒谬,但在编程世界中,我想没有其他方法可以让其他线程“看到”一个线程已经在执行一些代码行。有递归互斥锁等概念,但这个例子只是为了向您展示基本概念。希望这个例子能让你清楚地了解这个概念。]
With C++11 threading:
使用 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;
}
Compile and run using g++ -std=c++0x -pthread -o thread thread.cpp;./thread
编译并运行使用 g++ -std=c++0x -pthread -o thread thread.cpp;./thread
Instead of explicitly using lock
and unlock
, you can use brackets as shown here, if you are using a scoped lock for the advantage it provides. Scoped locks have a slight performance overhead though.
相反的明确使用lock
和unlock
,你可以用括号如下图所示,如果您正在使用范围的锁,因为它提供了优势。作用域锁虽然有轻微的性能开销。
With TBB:You'll need TBBto run the below program, but the intent of posting TBB code is that you understand the sequence of locking and unlocking just by looking at the simple code (could've shown scoped locking by not using acquire and release - which also is exception safe-, but this is clearer).
使用 TBB:您将需要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();
}
Note that tbb_thread.h
is deprecated. The replacement is shown here.
回答by San Jacinto
While a mutex may be used to solve other problems, the primary reason they exist is to provide mutual exclusion and thereby solve what is known as a race condition. When two (or more) threads or processes are attempting to access the same variable concurrently, we have potential for a race condition. Consider the following code
虽然互斥锁可用于解决其他问题,但它们存在的主要原因是提供互斥,从而解决所谓的竞争条件。当两个(或更多)线程或进程同时尝试访问同一个变量时,我们就有可能出现竞争条件。考虑以下代码
//somewhere long ago, we have i declared as int
void my_concurrently_called_function()
{
i++;
}
The internals of this function look so simple. It's only one statement. However, a typical pseudo-assembly language equivalent might be:
这个函数的内部结构看起来很简单。这只是一种说法。但是,典型的伪汇编语言等价物可能是:
load i from memory into a register
add 1 to i
store i back into memory
Because the equivalent assembly-language instructions are all required to perform the increment operation on i, we say that incrementing i is a non-atmoic operation. An atomic operation is one that can be completed on the hardware with a gurantee of not being interrupted once the instruction execution has begun. Incrementing i consists of a chain of 3 atomic instructions. In a concurrent system where several threads are calling the function, problems arise when a thread reads or writes at the wrong time. Imagine we have two threads running simultaneoulsy and one calls the function immediately after the other. Let's also say that we have i initialized to 0. Also assume that we have plenty of registers and that the two threads are using completely different registers, so there will be no collisions. The actual timing of these events may be:
因为对 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
What's happened is that we have two threads incrementing i concurrently, our function gets called twice, but the outcome is inconsistent with that fact. It looks like the function was only called once. This is because the atomicity is "broken" at the machine level, meaning threads can interrupt each other or work together at the wrong times.
发生的事情是我们有两个线程同时递增 i,我们的函数被调用了两次,但结果与事实不一致。看起来这个函数只被调用了一次。这是因为原子性在机器级别被“破坏”,这意味着线程可以相互中断或在错误的时间一起工作。
We need a mechanism to solve this. We need to impose some ordering to the instructions above. One common mechanism is to block all threads except one. Pthread mutex uses this mechanism.
我们需要一个机制来解决这个问题。我们需要对上面的说明进行一些排序。一种常见的机制是阻塞除一个线程之外的所有线程。Pthread 互斥锁使用这种机制。
Any thread which has to execute some lines of code which may unsafely modify shared values by other threads at the same time (using the phone to talk to his wife), should first be made acquire a lock on a mutex. In this way, any thread that requires access to the shared data must pass through the mutex lock. Only then will a thread be able to execute the code. This section of code is called a critical section.
任何线程必须执行一些可能不安全地同时修改其他线程共享值的代码行(使用电话与他的妻子交谈),应该首先获取互斥锁。这样,任何需要访问共享数据的线程都必须通过互斥锁。只有这样,线程才能执行代码。这部分代码称为临界区。
Once the thread has executed the critical section, it should release the lock on the mutex so that another thread can acquire a lock on the mutex.
一旦线程执行了临界区,它应该释放互斥锁上的锁,以便另一个线程可以获得互斥锁上的锁。
The concept of having a mutex seems a bit odd when considering humans seeking exclusive access to real, physical objects but when programming, we must be intentional. Concurrent threads and processes don't have the social and cultural upbringing that we do, so we must force them to share data nicely.
当考虑到人类寻求对真实物理对象的独占访问时,拥有互斥锁的概念似乎有点奇怪,但在编程时,我们必须是有意的。并发线程和进程没有我们那样的社会和文化教养,因此我们必须强迫它们很好地共享数据。
So technically speaking, how does a mutex work? Doesn't it suffer from the same race conditions that we mentioned earlier? Isn't pthread_mutex_lock() a bit more complex that a simple increment of a variable?
所以从技术上讲,互斥锁是如何工作的?它不会受到我们之前提到的相同竞争条件的影响吗?pthread_mutex_lock() 是不是比变量的简单增量更复杂一点?
Technically speaking, we need some hardware support to help us out. The hardware designers give us machine instructions that do more than one thing but are guranteed to be atomic. A classic example of such an instruction is the test-and-set (TAS). When trying to acquire a lock on a resource, we might use the TAS might check to see if a value in memory is 0. If it is, that would be our signal that the resource is in use and we do nothing (or more accurately, we wait by some mechanism. A pthreads mutex will put us into a special queue in the operating system and will notify us when the resource becomes available. Dumber systems may require us to do a tight spin loop, testing the condition over and over). If the value in memory is not 0, the TAS sets the location to something other than 0 without using any other instructions. It's like combining two assembly instructions into 1 to give us atomicity. Thus, testing and changing the value (if changing is appropriate) cannot be interrupted once it has begun. We can build mutexes on top of such an instruction.
从技术上讲,我们需要一些硬件支持来帮助我们。硬件设计师给我们的机器指令做不止一件事,但保证是原子的。这种指令的一个典型例子是测试和设置 (TAS)。当尝试获取资源上的锁时,我们可能会使用 TAS 可能会检查内存中的值是否为 0。如果是,这将是我们的信号,表明资源正在使用中,我们什么也不做(或更准确地说,我们通过某种机制等待。pthreads mutex 会将我们放入操作系统中的一个特殊队列中,并在资源可用时通知我们。笨蛋系统可能要求我们进行紧密的自旋循环,一遍又一遍地测试条件) . 如果内存中的值不为 0,则 TAS 将位置设置为 0 以外的值,而不使用任何其他指令。它' 就像将两个汇编指令合并为 1 以赋予我们原子性。因此,一旦开始测试和更改值(如果更改合适)就不能中断。我们可以在这样的指令之上构建互斥锁。
Note: some sections may appear similar to an earlier answer. I accepted his invite to edit, he preferred the original way it was, so I'm keeping what I had which is infused with a little bit of his verbiage.
注意:某些部分可能与之前的答案相似。我接受了他的编辑邀请,他更喜欢原来的方式,所以我保留了我所拥有的内容,其中注入了他的一些措辞。
回答by R.. GitHub STOP HELPING ICE
The best threads tutorial I know of is here:
我所知道的最好的线程教程在这里:
https://computing.llnl.gov/tutorials/pthreads/
https://computing.llnl.gov/tutorials/pthreads/
I like that it's written about the API, rather than about a particular implementation, and it gives some nice simple examples to help you understand synchronization.
我喜欢它写的是 API,而不是特定的实现,它提供了一些很好的简单示例来帮助您理解同步。
回答by fishfood
I stumbled upon this post recently and think that it needs an updated solution for the standard library's c++11 mutex (namely std::mutex).
我最近偶然发现了这篇文章,并认为它需要标准库的 c++11 互斥锁(即 std::mutex)的更新解决方案。
I've pasted some code below (my first steps with a mutex - I learned concurrency on win32 with HANDLE, SetEvent, WaitForMultipleObjects etc).
我在下面粘贴了一些代码(我使用互斥锁的第一步 - 我使用 HANDLE、SetEvent、WaitForMultipleObjects 等在 win32 上学习了并发性)。
Since it's my first attempt with std::mutex and friends, I'd love to see comments, suggestions and improvements!
由于这是我第一次尝试 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;
}
回答by chrisaycock
The function pthread_mutex_lock()
either acquiresthe mutex for the calling thread or blocks the thread until the mutex can be acquired. The related pthread_mutex_unlock()
releases the mutex.
该函数pthread_mutex_lock()
要么为调用线程获取互斥锁,要么阻塞线程直到可以获取互斥锁。相关的pthread_mutex_unlock()
释放互斥锁。
Think of the mutex as a queue; every thread that attempts to acquire the mutex will be placed on the end of the queue. When a thread releases the mutex, the next thread in the queue comes off and is now running.
将互斥体视为一个队列;每个尝试获取互斥锁的线程都将被放置在队列的末尾。当一个线程释放互斥锁时,队列中的下一个线程关闭并正在运行。
A critical sectionrefers to a region of code where non-determinism is possible. Often this because multiple threads are attempting to access a shared variable. The critical section is not safe until some sort of synchronization is in place. A mutex lock is one form of synchronization.
甲临界区是指代码的区域中的非确定性是可能的。通常这是因为多个线程试图访问共享变量。在某种同步到位之前,临界区是不安全的。互斥锁是同步的一种形式。
回答by Makis
You are supposed to check the mutex variable before using the area protected by the mutex. So your pthread_mutex_lock() could (depending on implementation) wait until mutex1 is released or return a value indicating that the lock could not be obtained if someone else has already locked it.
您应该在使用互斥锁保护的区域之前检查互斥锁变量。因此,您的 pthread_mutex_lock() 可以(取决于实现)等到 mutex1 被释放或返回一个值,表明如果其他人已经锁定了它,则无法获得该锁定。
Mutex is really just a simplified semaphore. If you read about them and understand them, you understand mutexes. There are several questions regarding mutexes and semaphores in SO. Difference between binary semaphore and mutex, When should we use mutex and when should we use semaphoreand so on. The toilet example in the first link is about as good an example as one can think of. All code does is to check if the key is available and if it is, reserves it. Notice that you don't really reserve the toilet itself, but the key.
Mutex 实际上只是一个简化的信号量。如果你阅读并理解了它们,你就会理解互斥锁。SO中有几个关于互斥体和信号量的问题。二元信号量和互斥量的区别,什么时候用互斥量,什么时候用信号量等等。第一个链接中的厕所示例与人们所能想到的一样好。所有代码所做的就是检查密钥是否可用,如果可用,则保留它。请注意,您并没有真正保留厕所本身,而是保留钥匙。
回答by Void
For those looking for the shortex mutex example:
对于那些寻找 shortex mutex 示例的人:
#include <mutex>
using namespace std;
int main() {
mutex m;
m.lock();
// do thread-safe stuff
m.unlock();
return 0;
}
回答by parasrish
SEMAPHORE EXAMPLE ::
信号量示例 ::
sem_t m;
sem_init(&m, 0, 0); // initialize semaphore to 0
sem_wait(&m);
// critical section here
sem_post(&m);
Reference : http://pages.cs.wisc.edu/~remzi/Classes/537/Fall2008/Notes/threads-semaphores.txt
参考:http: //pages.cs.wisc.edu/~remzi/Classes/537/Fall2008/Notes/threads-semaphores.txt