文件锁定与信号量

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_initsem_waitsem_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 测试的循环如下所示。 semwaitsemsignal 函数只是 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 的某种近似值,还是随机抛硬币)。当一个文件锁被释放时,我认为内核中不会发生同样的决策。

以上是关于文件锁定与信号量的主要内容,如果未能解决你的问题,请参考以下文章

锁定变量与信号量

使用嵌套异步调用锁定

SQL SERVER的锁机制——概述(锁的兼容性与可以锁定的资源)

PHP锁定/确保给定脚本在任何给定时间只运行一次

SylixOS文件记录锁使用

perf 是不是锁定配置文件用户空间互斥锁?