文件锁定与信号量
Posted
技术标签:
【中文标题】文件锁定与信号量【英文标题】:File Locking vs. Semaphores 【发布时间】:2010-08-25 17:52:41 【问题描述】:出于好奇,在 Linux 上实现进程间同步的首选方式是什么? sem*(2)
系列系统调用似乎有一个非常笨拙和过时的界面,而锁定文件的方法有三种 - fcntl()
、flock()
和 lockf()
。
内部差异是什么(如果有的话),您如何证明使用它们的合理性?
【问题讨论】:
【参考方案1】:两者都没有。 pthread_*
的实际版本(例如phtread_mutex_t
)都允许将变量放在通过shm_open
创建的共享段中。您只需在 init 调用中添加一些额外的参数即可。
如果不需要,不要使用信号量 (sem_t
),它们的级别太低并且会被 IO 等中断。
不要滥用文件锁定来进行进程间控制。它不是为此而设计的。特别是,您无法刷新文件元数据(例如锁),因此您永远不知道锁/解锁何时对第二个进程可见。
【讨论】:
我记得如果我在内核 2.4.x 上没记错的话,共享内存中的 pthread_mutex_t 有时会造成麻烦。你知道吗? @DarkDust:2.6.something 之前的内核版本(但很多年前)有不同的 pthread 实现,这实际上不适合进程间控制。这是历史。无论如何,如果实现不支持pshared
属性,相应的init
调用会在返回时告诉您。
你会用这种技术牺牲便携性吗?
首先问题是关于 linux... 然后,sem_init
调用的 pshared
参数的规范是在 POSIX 中指定的,因此尝试它的事实是可移植的。如果不支持pshared
,则符合POSIX 的实现可以做的是返回错误ENOSYS
。对于pthread_mutex_t
,如果函数pthread_mutexattr_setpshared
存在,可以事先检查。似乎例如在 OS X 上没有,例如
【参考方案2】:
正如 DarkDust 所指出的,您正在经历丰富的历史中的大量选择。值得我的决策树是这样的:
当一次只有一个进程/线程可以访问时使用互斥锁。
当两个或更多(但仍然是有限的)进程/线程可以使用资源时使用信号量。
使用 POSIX 信号量,除非你真的需要 SYSV 信号量具有的东西 - 例如。 UNDO、上次操作的PID等
对文件使用文件锁定,或者如果上述方法在某些方面不符合您的要求。
【讨论】:
【参考方案3】:不同的锁定/信号量实现都在不同的系统上实现。在 System V Unix 上,您有 semget
/semop
,POSIX 使用 sem_init
、sem_wait
和 sem_post
定义了不同的实现。据我所知,flock
起源于 4.2BSD。
由于它们都获得了一定的意义,Linux 现在支持它们以使移植变得容易。此外,flock
是互斥体(锁定或解锁),但 sem*
函数(SysV 和 POSIX)是信号量:它们允许应用程序授予多个并发进程访问权限,例如您可以允许使用信号量同时访问 4 个进程的资源。您可以使用信号量实现互斥锁,但反之则不行。我记得在 Marc J. Rochkind 的出色“高级 UNIX 编程” 中,他演示了如何通过信号量在进程之间传输数据(非常低效,他这样做只是为了证明可以做到)。但我找不到任何关于效率的可靠信息。
我想这更像是“随心所欲”。
【讨论】:
为什么不能使用互斥锁实现信号量? @Anurag Uniyal:因为互斥体只有两种状态:锁定或解锁。信号量是一个计数器,因此具有两个以上的状态。 这似乎不对。我看不出为什么信号量不能使用互斥锁来完成。 @Darkdust,所以你是说位不能代表一个字节,因为位只有两种状态,而字节有很多? @DarkDust(我的意思是比特),无论如何semaphore(3).acquire() == mutex1. acquire(block=False) or mutex2. acquire(block=False) or mutex3. acquire(block=False) or mutex_random.acquire()
不会这样做?【参考方案4】:
一个潜在的显着差异可能是资源分配的公平性。我不知道semget/semop
系列的实现细节,但我怀疑就调度而言,它通常被实现为“传统”信号量。一般来说,我相信释放的线程是在 FIFO 的基础上处理的(第一个等待信号量的线程首先被释放)。我认为文件锁定不会发生这种情况,因为我怀疑(再次只是猜测)处理不是在内核级别执行的。
我有现有的代码来测试 IPC 的信号量,因此我比较了两种情况(一种使用 semop
,另一种使用 lockf
)。我做了一个穷人的测试,只是跑到应用程序的实例。共享信号量用于同步启动。运行 semop 测试时,两个进程几乎同步完成了 300 万次循环。另一方面,lockf 循环就没有那么公平了。一个进程通常会完成,而另一个进程只完成了一半的循环。
semop 测试的循环如下所示。 semwait
和 semsignal
函数只是 semop
调用的包装器。
ct = myclock();
for ( i = 0; i < loops; i++ )
ret = semwait( "test", semid, 0 );
if ( ret < 0 ) perror( "semwait" ); break;
if (( i & 0x7f ) == 0x7f )
printf( "\r%d%%", (int)(i * 100.0 / loops ));
ret = semsignal( semid, 0 );
if ( ret < 0 ) perror( "semsignal" ); break;
printf( "\nsemop time: %d ms\n", myclock() - ct );
两种方法的总运行时间大致相同,尽管有时由于调度的不公平性,lockf 版本实际上总体上更快。一旦第一个进程完成,另一个进程将拥有大约 150 万次迭代的无争议访问权,并且运行速度极快。
当运行无竞争(单进程获取和释放锁)时,semop 版本更快。 100 万次迭代大约需要 2 秒,而 lockf 版本大约需要 3 秒。
这是在以下版本上运行的:
[]$ uname -r
2.6.11-1.1369_FC4smp
【讨论】:
AFAIK 系统信号量完全依赖于操作系统调度程序,没有自己的智能。虽然可以设置整体线程调度策略,但线程释放顺序在任何实际意义上都是不确定的。 @Duck,这当然是真的;期望线程按特定顺序释放是错误的。但是你的信号量依赖于操作系统是关键点。因为操作系统正在做出决定,所以它可以应用它想要的任何“公平”规则(无论是 FIFO 的某种近似值,还是随机抛硬币)。当一个文件锁被释放时,我认为内核中不会发生同样的决策。以上是关于文件锁定与信号量的主要内容,如果未能解决你的问题,请参考以下文章