【问题标题】:File Locking vs. Semaphores文件锁定与信号量
【发布时间】:2010-08-25 17:52:41
【问题描述】:

出于好奇,在 Linux 上实现进程间同步的首选方式是什么? sem*(2) 系列系统调用似乎有一个非常笨拙和过时的界面,而锁定文件的方法有三种 - fcntl()flock()lockf()

内部差异是什么(如果有的话),您如何证明使用它们的合理性?

【问题讨论】:

    标签: c linux unix interprocess


    【解决方案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 的某种近似值,还是随机抛硬币)。当一个文件锁被释放时,我认为内核中不会发生同样的决策。
      猜你喜欢
      • 1970-01-01
      • 2010-09-16
      • 2019-04-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-15
      • 2021-03-20
      相关资源
      最近更新 更多