所以在我的日子里,我开始思考windows / linux如何实现互斥锁,我已经以100种不同的方式实现了这种同步器,在许多不同的建筑中,但从未想过它是如何实现的在大型操作系统中实现,例如在ARM世界中,我使一些同步器禁用了中断,但我总是认为这不是一个非常好的方法。
我试图“游泳”throgh linux内核,但就像一个我看不到任何满足我好奇心的东西。我不是线程专家,但我掌握了它的所有基本概念和中间概念。 那么有谁知道如何实现互斥锁?
答案 0 :(得分:3)
快速查看来自one Linux distribution的代码似乎表明它是使用互锁比较和交换实现的。因此,从某种意义上说,操作系统并没有真正实现它,因为互锁操作可能是在硬件级别处理的。
编辑正如Hans指出的那样,互锁交换以原子方式进行比较和交换。 Here is documentation for the Windows version。为了好玩,我刚刚写了一个小测试来展示一个创建这样的互斥体的一个非常简单的例子。这是一个简单的获取和发布测试。
#include <windows.h>
#include <assert.h>
#include <stdio.h>
struct homebrew {
LONG *mutex;
int *shared;
int mine;
};
#define NUM_THREADS 10
#define NUM_ACQUIRES 100000
DWORD WINAPI SomeThread( LPVOID lpParam )
{
struct homebrew *test = (struct homebrew*)lpParam;
while ( test->mine < NUM_ACQUIRES ) {
// Test and set the mutex. If it currently has value 0, then it
// is free. Setting 1 means it is owned. This interlocked function does
// the test and set as an atomic operation
if ( 0 == InterlockedCompareExchange( test->mutex, 1, 0 )) {
// this tread now owns the mutex. Increment the shared variable
// without an atomic increment (relying on mutex ownership to protect it)
(*test->shared)++;
test->mine++;
// Release the mutex (4 byte aligned assignment is atomic)
*test->mutex = 0;
}
}
return 0;
}
int main( int argc, char* argv[] )
{
LONG mymutex = 0; // zero means
int shared = 0;
HANDLE threads[NUM_THREADS];
struct homebrew test[NUM_THREADS];
int i;
// Initialize each thread's structure. All share the same mutex and a shared
// counter
for ( i = 0; i < NUM_THREADS; i++ ) {
test[i].mine = 0; test[i].shared = &shared; test[i].mutex = &mymutex;
}
// create the threads and then wait for all to finish
for ( i = 0; i < NUM_THREADS; i++ )
threads[i] = CreateThread(NULL, 0, SomeThread, &test[i], 0, NULL);
for ( i = 0; i < NUM_THREADS; i++ )
WaitForSingleObject( threads[i], INFINITE );
// Verify all increments occurred atomically
printf( "shared = %d (%s)\n", shared,
shared == NUM_THREADS * NUM_ACQUIRES ? "correct" : "wrong" );
for ( i = 0; i < NUM_THREADS; i++ ) {
if ( test[i].mine != NUM_ACQUIRES ) {
printf( "Thread %d cheated. Only %d acquires.\n", i, test[i].mine );
}
}
}
如果我注释掉对InterlockedCompareExchange
调用的调用,并让所有线程以free-for-all方式运行增量,那么结果会导致失败。运行10次,例如没有互锁的比较调用:
shared = 748694 (wrong)
shared = 811522 (wrong)
shared = 796155 (wrong)
shared = 825947 (wrong)
shared = 1000000 (correct)
shared = 795036 (wrong)
shared = 801810 (wrong)
shared = 790812 (wrong)
shared = 724753 (wrong)
shared = 849444 (wrong)
奇怪的是,有一次结果显示现在的争用不正确。那可能是因为没有“每个人都开始”同步;也许所有线程都是按顺序启动和完成的。但是当我有InterlockedExchangeCall时,它运行没有失败(或者至少它运行了100次而没有失败......这并不能证明我没有在示例中写出一个微妙的错误。)
答案 1 :(得分:1)
Here是来自实施它的人的讨论......非常有趣,因为它显示了权衡..
Linus T的几篇帖子......当然
答案 2 :(得分:1)
在早期的POSIX之前,我曾经通过使用本机模式字(例如16或32位字)和潜伏在每个严重处理器上的测试和设置指令来实现同步。该指令保证测试一个字的值并将其设置在一条原子指令中。这为自旋锁提供了基础,并且可以构建同步函数的层次结构。最简单的当然只是一个执行繁忙等待的自旋锁,而不是暂时性同步的选项,然后是一个自旋锁,它在每次迭代时丢弃过程时间片以降低系统影响。可以通过进入内核调度代码来构建信号量,互斥锁,监视器等概念性概念。
我记得主要用途是实现消息队列以允许多个客户端访问数据库服务器。另一个是非常早期的实时赛车结果和计时系统在一个非常原始的16位机器和操作系统上。
这些天我使用Pthreads和Semaphores以及Windows Events / Mutexes(mutices?)等,并没有考虑它们是如何工作的,虽然我必须承认,已经在机房内确实给了一个直观的感觉更好,更有效的多处理。
答案 3 :(得分:0)
在windows世界中。 使用Compare Exchange实现windows vista mas之前的互斥锁,将互斥锁的状态从Empty更改为BeingUsed,在互斥锁上进入等待的其他线程CAS将明显失败,必须将其添加到互斥队列中通知。队列的那些操作(添加/删除/检查)将受到windows内核中的公共锁的保护。 在Windows XP之后,由于性能原因,互斥锁开始使用自旋锁,这是一个自我牺牲的对象。
在unix世界中,我并没有得到太多的东西,但可能与windows 7非常相似。
最后,对于在单个处理器上运行的内核,最好的方法是在进入临界区时禁用中断,然后在退出时重新启用。