【发布时间】:2010-08-25 17:52:41
【问题描述】:
出于好奇,在 Linux 上实现进程间同步的首选方式是什么? sem*(2) 系列系统调用似乎有一个非常笨拙和过时的界面,而锁定文件的方法有三种 - fcntl()、flock() 和 lockf()。
内部差异是什么(如果有的话),您如何证明使用它们的合理性?
【问题讨论】:
标签: c linux unix interprocess
出于好奇,在 Linux 上实现进程间同步的首选方式是什么? sem*(2) 系列系统调用似乎有一个非常笨拙和过时的界面,而锁定文件的方法有三种 - fcntl()、flock() 和 lockf()。
内部差异是什么(如果有的话),您如何证明使用它们的合理性?
【问题讨论】:
标签: c linux unix interprocess
两者都没有。 pthread_* 的实际版本(例如phtread_mutex_t)都允许将变量放在通过shm_open 创建的共享段中。您只需在 init 调用中添加一些额外的参数即可。
如果不需要,不要使用信号量 (sem_t),它们的级别太低并且会被 IO 等中断。
不要滥用文件锁定来进行进程间控制。它不是为此而设计的。特别是,您无法刷新文件元数据(例如锁),因此您永远不知道锁/解锁何时对第二个进程可见。
【讨论】:
pshared 属性,相应的init 调用会在返回时告诉您。
sem_init 调用的 pshared 参数的规范是在 POSIX 中指定的,因此尝试它的事实是可移植的。如果不支持pshared,则符合POSIX 的实现可以做的是返回错误ENOSYS。对于pthread_mutex_t,如果函数pthread_mutexattr_setpshared 存在,可以事先检查。似乎例如在 OS X 上没有,例如
正如 DarkDust 所指出的,您正在经历丰富的历史中的大量选择。值得我的决策树是这样的:
当一次只有一个进程/线程可以访问时使用互斥锁。
当两个或更多(但仍然是有限的)进程/线程可以使用资源时使用信号量。
使用 POSIX 信号量,除非你真的需要 SYSV 信号量具有的东西 - 例如。 UNDO、上次操作的PID等
对文件使用文件锁定,或者如果上述方法在某些方面不符合您的要求。
【讨论】:
不同的锁定/信号量实现都在不同的系统上实现。在 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 编程” 中,他演示了如何通过信号量在进程之间传输数据(非常低效,他这样做只是为了证明可以做到)。但我找不到任何关于效率的可靠信息。
我想这更像是“随心所欲”。
【讨论】:
semaphore(3).acquire() == mutex1. acquire(block=False) or mutex2. acquire(block=False) or mutex3. acquire(block=False) or mutex_random.acquire() 不会这样做?
一个潜在的显着差异可能是资源分配的公平性。我不知道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
【讨论】: