【问题标题】:Self-written Mutex for 2+ Threads2+线程的自写互斥锁
【发布时间】:2015-09-06 07:15:03
【问题描述】:

我已经编写了以下代码,到目前为止,在我的所有测试中,似乎我已经为我的 4 个线程编写了一个工作互斥锁,但我想就我的解决方案的有效性征求其他人的意见。

typedef struct Mutex{
    int turn;
    int * waiting;
    int num_processes;
} Mutex;

void enterLock(Mutex * lock, int id){   
    int i;
    for(i = 0; i < lock->num_processes; i++){
        lock->waiting[id] = 1;
        if (i != id && lock->waiting[i])
            i = -1;
        lock->waiting[id] = 0;
    }
    printf("ID %d Entered\n",id);
}

void leaveLock(Mutex * lock, int id){
    printf("ID %d Left\n",id);
    lock->waiting[id] = 0;
}

void foo(Muted * lock, int id){
    enterLock(lock,id);
    // do stuff now that i have access
    leaveLock(lock,id);
}

【问题讨论】:

  • 虽然这段代码不完整(或者非常错误).. Mutex.waiting 永远不会指向某个已定义的地方。除此之外,尝试在 C 提供的非原子内容上编写自己的原子原语绝对是一个坏主意,而且要证明它可能出错的地方需要做很多工作(但总是可行的)。 【关键是找到随机线程切换最有问题的点】
  • 添加到我的评论...对于可靠的代码,请使用pthread_mutex_t ...实现可能会有所不同,但您永远找不到基于“纯”C的实现,因为它总是需要一些原子原语。
  • 据我所知,没有什么能阻止线程 B 在线程 A 已经进入它之后和线程 A 离开它之前进入同一个互斥锁。 Mutex的状态无论是锁定还是解锁都是一样的。
  • 据我所知,for-loop 中的 nothing 符合可见的副作用,因此编译器可以随意重新排序访问。你怎么可能听说过mutex,却对原子性一无所知?
  • 好的@AndrewGrant——然后阅读原子操作。例如,您需要一条汇编指令来测试内存位置的值,同时尽可能修改它(不可中断)。 C 没有给你那个。

标签: c multithreading pthreads posix mutex


【解决方案1】:

我觉得有必要在这里写一个答案,因为这个问题很好,考虑到它可以帮助其他人理解互斥的一般问题。在您的情况下,您已经走了很长一段路来隐藏这个问题,但您无法避免它。归结为:

01  /* pseudo-code */
02  if (! mutex.isLocked())
03        mutex.lock();

您总是需要在行 0203 之间进行线程切换。所以有一种可能的情况是两个线程发现mutex 解锁并在此之后被中断......只是稍后恢复并单独锁定这个互斥锁。您将有两个线程同时进入临界区。

因此,实现可靠互斥绝对需要的是一个原子操作,它测试一个条件,同时设置一个值,同时没有任何被中断的机会。

01  /* pseudo-code */
02  while (! test_and_lock(mutex));

只要这个test_and_lock 函数不能被中断,你的实现就是安全的。在 之前,C 没有提供类似的东西,所以 pthreads 的实现需要使用例如汇编或特殊编译器内在函数。有了,终于有了一种“标准”的方式来编写这样的原子操作,但我不能在这里举个例子,因为我没有这样做的经验。对于一般用途,pthreads 库将为您提供所需的内容。

编辑:当然,这仍然是简化的——在多处理器场景中,您需要确保即使内存访问也是互斥的。

【讨论】:

    【解决方案2】:

    我在你的代码中看到的问题:

    mutex 背后的想法是提供互斥,这意味着当thread_a 处于临界区时,thread_b 必须等待(以防他也想进入)thread_a

    这个等待部分应该在enterLock函数中实现。但是你所拥有的是一个for 循环,它可能在thread_a 从临界区完成之前结束,因此thread_b 也可以进入,因此你不能有互斥。

    解决方法:

    Peterson's algorithm 或 Dekker's(more complicated) 为例,他们在那里所做的就是所谓的busy waiting,它基本上是一个while 循环,上面写着: while(i can't enter) { do nothing and wait...}

    【讨论】:

    • 我很困惑为什么你认为我的 for 循环可能会在 thread_a 完成之前结束?我会编辑我的帖子以显示更多信息
    • in for(i = 0; i &lt; lock-&gt;num_processes; i++) , i 运行 num_processes 次,然后 for loop 到此结束,然而,同时 thread_a 不一定完成,它可以执行繁重的任务或完成
    • 它运行了无数次,直到一次迭代显示每个 lock->waiting[i] 都是假的。你可能错过了阅读 for 循环
    • @AndrewGrant 测试可能与更改重叠......这是所有尝试在没有原子原语的情况下进行互斥的基本问题
    【解决方案3】:

    您完全忽略了内存模型的话题。除非您使用的是具有顺序一致内存模型的机器(今天的 PC CPU 都不是),否则您的代码是不正确的,因为一个线程执行的任何存储不一定立即对其他 CPU 可见。但是,这似乎是您的代码中的一个假设。

    底线:使用操作系统提供的现有同步原语或运行时库(如 POSIX 或 Win32 API),不要自作聪明地自行实现。除非您有多年的并行编程经验以及对 CPU 体系结构的深入了解,否则您很有可能最终得到一个不正确的实现。调试并行程序可能是地狱......

    【讨论】:

    • 机器的内存模型是什么并不重要:编译器可能会重新排序内存访问。
    【解决方案4】:

    enterLock() 返回后,Mutex 对象的状态与函数调用前相同。因此,即使在第一个线程释放它调用leaveLock() 之前,它也不会阻止第二个线程进入同一个 Mutex 对象。没有互斥性。

    【讨论】:

      猜你喜欢
      • 2013-01-31
      • 2012-12-25
      • 1970-01-01
      • 2014-05-02
      • 2010-12-08
      • 1970-01-01
      • 2012-07-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多