面试问:你是如何编写互斥锁的
InterviewQ: How do you code a mutex?
我很不幸失业了,最近一直在面试。我现在两次面对同样的问题,两次被问到这个问题时都不知所措。
"如何编写互斥体代码"?
从概念上讲,我理解互斥锁锁定代码的某个部分,这样多个线程就不能同时进入关键部分,从而消除了数据竞争。第一次我被要求从概念上描述我将如何编码,第二次我被问到如何编码。我一直在谷歌上搜索,但没有找到任何答案。。。有人能帮忙吗?
谢谢。
有很多方法可以实现互斥锁,但它通常从cpu架构提供原子加和核减的一些概念这一基本前提开始。也就是说,可以对内存中的整数变量执行加法运算(并返回结果),而不会被另一个试图访问相同内存位置的线程破坏。或者至少是"原子增量"answers"原子减量"。
例如,在现代的英特尔芯片上,有一种叫做XADD
的指令。当与LOCK
前缀结合使用时,它会自动执行,并使其他内核中的缓存值无效。gcc为这个指令实现了一个名为__sync_add_and_fetch
的包装器。Win32实现了一个类似的函数InterlockedIncrement
。两者都只是在暗中呼叫LOCK XADD
。其他CPU架构应该提供类似的功能。
因此,最基本的互斥锁可以这样实现。这通常被称为"旋转"锁。而且这个廉价的版本不提供递归进入锁的能力。
// A correct, but poorly performant mutex implementation
void EnterLock(int* lock)
{
while (true)
{
int result = LOCK_XADD(lock,1); // increment the value in lock and return the result atomically
if (result == 1)
{
// if the value in lock was successfully incremented
// from 0 to 1 by this thread. It means this thread "acquired" the lock
return;
}
LOCK XADD(lock,-1); // we didn't get the lock - decrement it atmoically back to what it was
sleep(0); // give the thread quantum back before trying again
}
}
void LeaveLock(int* lock)
{
LOCK XADD(lock,-1); // release the lock. Assumes we successfully acquired it correctly with EnterLock above
}
以上内容的"旋转"性能较差,无法保证任何公平性。优先级较高的线程可以继续赢得EnterLock对优先级较低的线程的争夺。程序员可能会犯错误,用以前没有调用EnterLock的线程调用LeaveLock。您可以扩展以上内容,以便对一个数据结构进行操作,该数据结构不仅包括锁整数,而且还保留了所有者线程id和递归计数的记录。
实现互斥的第二个概念是,操作系统可以提供一个等待和通知服务,这样线程在所有者线程释放它之前就不必旋转。等待锁定的线程或进程可以向操作系统注册自己,使其进入睡眠状态,直到所有者线程释放为止。用操作系统的术语来说,这被称为信号量。此外,操作系统级别的信号量还可以用于实现不同进程之间的锁定,以及CPU不提供原子添加的情况。并且可以用于保证试图获取锁的多个线程之间的公平性。
大多数实现都会尝试旋转多次,然后再返回到进行系统调用。
我不会说这是一个愚蠢的问题。在职位的任何抽象层面上。在高级上,您只需说,您使用标准库或任何线程库。如果你申请编译器开发人员的职位,你需要了解它是如何实际工作的,以及实现需要什么。
要实现互斥,您需要一个锁定机制,也就是说,您需要有一个可以标记为跨所有线程占用的资源。这不是小事。您需要记住,两个核心共享内存,但它们有缓存。这条信息必须保证是真实的。因此,您确实需要对硬件的支持来确保原子性。
如果你研究clang的实现,他们(至少在一次情况下)将实现卸载到pthreads,线程支持中的typedefs:
#if defined(_LIBCPP_HAS_THREAD_API_PTHREAD)
# include <pthread.h>
# include <sched.h>
#elif defined(_LIBCPP_HAS_THREAD_API_WIN32)
#include <Windows.h>
#include <process.h>
#include <fibersapi.h>
#endif
若您深入研究pthreads-reo,您可以找到互锁操作的asm实现。它们依赖于使操作原子化的lock
asm关键字,即没有其他线程可以同时执行它们。这消除了比赛条件,并保证了连贯性。
基于此,您可以构建一个lock
,用于mutex
实现。
- 函数何时会在c++中包含stack_Unwind_Resume调用
- 正在为Xtensa simcall函数编写回调函数
- Python中的for循环与C++有何不同
- 在程序中编写脚本来编写和编译代码
- 编写时C++中的输入重定向问题
- 在C++中,如何在类和函数(可能是模板化的)的头中编写完整的实现
- c++多进程编写一个唯一的文件
- 如何用C++编写BFS函数
- 编写代码时C++出现错误:错误 1 错误 C2601:'circle':本地函数定义是非法的
- 如何编写一个使用n倍三元条件语句的C++布尔函数
- 为C++03编译器编写部分unique_ptr,该编译器与较新的编译器在公共代码库上运行
- 如何为 C 型字符串数组编写 getter 和 setter?
- 如何在没有函数的情况下编写此代码并使C++更简单?
- 编写一个函数以使用 n 百分比的 CPU 使用率
- 如何使用OpenCV-C++编写*.mp4视频?
- 3-3. 编写一个程序来计算每个不同单词在其输入中出现的次数
- 我的目标是编写一个程序来计算和存储字符串在字符数组中出现的位置
- 如何编写带有异常的构造函数
- 字符 * 和字符串在C++中有何不同?(在描述中编写代码)
- 在C#中为windows API编写COM服务器,从何开始