InterviewQ:你如何编码互斥体?

时间:2017-02-12 19:38:22

标签: c++ multithreading mutex

我不幸失业,最近一直在采访。我现在两次面对同样的问题,两次失去了我被问到这个问题。

  

"您如何编码互斥锁"?

从概念上讲,我理解互斥锁会锁定代码的某个部分,因此多个线程无法同时进入临界区,从而消除了数据争用。我第一次被要求在概念上描述我将如何编码,第二次被要求编码。我一直在谷歌搜索,没有找到任何答案......任何人都可以帮忙吗?

感谢。

2 个答案:

答案 0 :(得分:0)

有很多方法可以实现互斥锁,但它通常以cpu架构提供 atomic add atomic subtract 的概念为基本前提。也就是说,可以对存储器中的整数变量进行加法运算(并返回结果),而不会被试图访问相同存储器位置的另一个线程破坏。或者至少是“原子增量”和“原子减量”。

例如,在现代英特尔芯片上,有一条名为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和递归计数的记录。

实现互斥锁的第二个概念是操作系统可以提供等待和通知服务,使得线程在所有者线程释放之前不必旋转。等待锁定的线程或进程可以向操作系统注册自己以使其进入休眠状态,直到所有者线程释放它为止。在OS术语中,这称为semaphore。此外,OS级别信号量还可用于实现跨不同进程的锁定以及CPU不提供原子添加的情况。并且可以用于保证多个线程尝试获取锁定之间的公平性。

大多数实现都会尝试旋转多次尝试,然后再回到系统调用。

答案 1 :(得分:0)

我不会说这是一个愚蠢的问题。在任何级别的职位抽象。在高级别上,您只需说,您可以使用标准库或任何线程库。如果您申请担任编辑开发人员的职位,您需要了解它的实际工作方式以及实施所需的内容。

要实现互斥锁,您需要一种锁定机制,即您需要拥有一个可以标记为跨所有线程的资源。这不是微不足道的。您需要记住两个内核共享内存,但它们有缓存。必须保证这条信息是实际的。因此,您确实需要对硬件的支持以确保原子性。

如果你采用clang的实现,他们会将实现卸载(至少在一次)实现到pthreads,typedefs in threading support

#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 repo,你可以找到联锁操作的asm实现。它们依赖lock asm关键字使操作成为原子,即没有其他线程可以同时执行它们。这消除了比赛条件,并保证了一致性。

基于此,您可以构建lock,您可以将其用于mutex实施。