我正在开发一个用户级线程库作为项目的一部分。我想出了一种实现互斥的方法。我希望在继续之前看到你的观点。基本上,我需要在我的库中实现3个函数
mutex_init,mutex_lock和mutex_unlock
我认为我的mutex_t结构看起来像
typedef struct
{
int available; //indicates whether the mutex is locked or unlocked
queue listofwaitingthreads;
gtthread_t owningthread;
}mutex_t;
在我的mutex_lock函数中,我将首先检查互斥锁是否在while循环中可用。如果不是,我将为下一个要执行的线程生成处理器。
在我的mutex_unlock函数中,我将检查所有者线程是否是当前线程。如果是,我将设置为0.
这是怎么回事?还有,僵局呢?我应该在用户级库中处理这些条件,还是应该让应用程序员正确编写代码?
答案 0 :(得分:8)
这不起作用,因为你有竞争条件。如果2个线程同时尝试捕获锁,则两者都会看到available == 0
,并且两者都认为他们成功获取了互斥锁。
如果要正确执行此操作,并且不使用已存在的锁,则必须访问TAS,CAS等硬件操作。
有些算法可以在没有这些硬件支持的情况下为您提供互斥,但是它们会做出一些错误的假设。有关这方面的更多细节,我强烈建议阅读Herlihy和Shavit的多处理器编程艺术,第7章。
你不应该担心这个级别的死锁 - 互斥锁应该足够简单,并且有一些假设,使用它们的程序员应该小心不要导致死锁(高级互斥锁可以检查自死锁,意思是调用锁两次而不调用中间解锁的线程。)
答案 1 :(得分:0)
不仅你必须进行原子操作来读取和修改标志(正如Eran指出的那样),你还必须注意你的队列能够进行并发访问。这并非完全无足轻重,有点像鸡蛋和鸡蛋问题。
但是如果你真的通过旋转来实现它,你甚至不需要有这样的队列。然而,锁的访问顺序主要是随机的。
可能只是屈服也是不够的,如果线程持有超过一些处理器周期的锁,这可能会非常昂贵。考虑使用等待时间值较短的nanosleep
。
答案 2 :(得分:0)
通常,互斥锁实现应如下所示:
锁定:
while (trylock()==failed) {
atomic_inc(waiter_cnt);
atomic_sleep_if_locked();
atomic_dec(waiter_cnt);
}
的tryLock:
return atomic_swap(&lock, 1);
解锁:
atomic_store(&lock, 0);
if (waiter_cnt) wakeup_sleepers();
如果你想要递归的互斥锁,可以同步自己的破坏的互斥锁(例如,一旦你获得锁定就释放互斥锁是安全的)等等,事情会变得更加复杂。
请注意,atomic_sleep_if_locked
和wakeup_sleepers
对应于Linux上的FUTEX_WAIT
和FUTEX_WAKE
操作。其他原子可能是CPU指令,但可能是系统调用或内核辅助用户空间函数代码,如Linux / ARM和0xffff0fc0原子比较和交换调用。
答案 3 :(得分:0)
您不需要用户级线程库的原子指令,因为所有线程都将是同一进程的用户级线程。实际上,当您的进程被赋予执行时间片时,您在该时间片期间运行多个线程但在同一处理器上运行。因此,同时没有两个线程将在库函数中。考虑到互斥锁的功能已经存在于库中,因此可以保证互斥。