多个线程能够同时获取文件锁

12

我曾经以为flock(2)是线程安全的,但最近在代码中遇到一种情况,多个线程可以对同一文件获取锁,这些锁都是使用c api flock获取的。 进程25554是一个具有20个线程的多线程应用程序,当死锁发生时,持有同一文件锁的线程数量会有所变化。多线程应用程序testEvent将写入文件,而推入则从文件读取内容。不幸的是,lsof不打印LWP值,因此我无法找到持有锁的线程。 当下面提到的条件发生时,进程和线程都会在flock调用上卡住,如pid 25569和25554上的pstackstrace调用所显示的那样。你有什么建议可以在RHEL 4.x中克服这个问题吗?

我想更新的一件事是,flock并非每次都表现不良,只有当消息的传输速率超过2 mbps时,才会出现这种死锁问题,在低于该速率的情况下一切正常。我保持了num_threads = 20,size_of_msg = 1000字节的常量,只是变化了每秒传输的消息数,从10条消息开始到100条消息,即20 * 1000 * 100 = 2 mbps,当我将消息数量增加到150时,就会出现flock问题。

我想问一下您对flockfile c api的看法。

 sudo lsof filename.txt
    COMMAND       PID     USER     FD       TYPE     DEVICE     SIZE   NODE       NAME
    push         25569    root     11u       REG      253.4      1079   49266853   filename.txt
    testEvent    25554    root     27uW      REG      253.4      1079   49266853   filename.txt
    testEvent    25554    root     28uW      REG      253.4      1079   49266853   filename.txt
    testEvent    25554    root     29uW      REG      253.4      1079   49266853   filename.txt
    testEvent    25554    root     30uW      REG      253.4      1079   49266853   filename.txt

多线程测试程序将调用 write_data_lib_func 库函数。

void* sendMessage(void *arg)  {

int* numOfMessagesPerSecond = (int*) arg;
std::cout <<" Executing p thread id " << pthread_self() << std::endl;
 while(!terminateTest) {
   Record *er1 = Record::create();
   er1.setDate("some data");

   for(int i = 0 ; i <=*numOfMessagesPerSecond ; i++){
     ec = _write_data_lib_func(*er1);
     if( ec != SUCCESS) {
       std::cout << "write was not successful" << std::endl;

     }

   }
   delete er1;
   sleep(1);
 }

 return NULL;

上述方法将在测试主函数中的pthreads中被调用。

for (i=0; i<_numThreads ; ++i) {
  rc = pthread_create(&threads[i], NULL, sendMessage, (void *)&_num_msgs);
  assert(0 == rc);

}

以下是写入者/读取者的源代码,由于保密原因我不想直接剪切粘贴,写入者源代码将会在进程中被多个线程访问。

int write_data_lib_func(Record * rec) {      
if(fd == -1 ) {  
    fd = open(fn,O_RDWR| O_CREAT | O_APPEND, 0666);
} 
if ( fd >= 0 ) {
   /* some code */ 

   if( flock(fd, LOCK_EX) < 0 ) {
     print "some error message";
   }
   else { 
    if( maxfilesize) {
      off_t len = lseek ( fd,0,SEEK_END);
      ...
      ... 
      ftruncate( fd,0);
      ...
      lseek(fd,0,SEEK_SET); 
   } /* end of max spool size */ 
   if( writev(fd,rec) < 0 ) {
     print "some error message" ; 
   }

   if(flock(fd,LOCK_UN) < 0 ) {
   print some error message; 
   } 

在读取器方面,存在一个没有线程的守护进程。

int readData() {
    while(true) {
      if( fd == -1 ) {
         fd= open (filename,O_RDWR);
      }
      if( flock (fd, LOCK_EX) < 0 ) { 
        print "some error message"; 
        break; 
      } 
      if( n = read(fd,readBuf,readBufSize)) < 0 ) { 
        print "some error message" ;
        break;
      }  
      if( off < n ) { 
        if ( off <= 0 && n > 0 ) { 
          corrupt_file = true; 
        } 
        if ( lseek(fd, off-n, SEEK_CUR) < 0 ) { 
          print "some error message"; 
        } 
        if( corrupt_spool ) {  
          if (ftruncate(fd,0) < 0 ) { 
             print "some error message";
             break;
           }  
        }
      }
      if( flock(fd, LOCK_UN) < 0 ) 
       print some error message ;
      }  
   }     
}

你能发布调用 flock 的代码吗?最好附上一个 简单的 测试程序。 - phihag
据我所知,咨询锁不能保证一致性。http://www.gsp.com/cgi-bin/man.cgi?section=2&topic=flock - zengr
2个回答

11

flock(2)被记录为“如果另一个进程持有不兼容的锁,则阻塞”,并且“由flock()创建的锁与打开的文件表项相关联”,因此应该预期同一进程的几个线程使用flock的锁不会相互影响。(flock文档没有提到线程)。

因此,对于您来说,解决方案应该很简单:将每个可flock的文件描述符关联一个pthread_mutex_t,并用该互斥锁保护对flock的调用。 如果需要读写锁定,则还可以使用pthread_rwlock_t


谢谢你的回答,我确信我也在考虑使用互斥锁与flock进行关联,让我困扰的是flock(2)是建立在fcntl(2)之上的,而fcntl(2)和lockf(2)声称是线程安全的,所以对我来说这是个意外。 - user1235176
我现在尝试了这个建议,为了同步flock调用,增加了额外的pthread_mutex_t。现在的情况是19个线程都被卡在获取pthread_mutex_t上,而1个线程则无法使用flock获得文件的独占锁,并且它被卡在flock(fd,LOCK_EX)这一行。 - user1235176
从flock失败中得到了什么errno? - zoska

3

来自Linux man flock(2)页面:

flock()创建的锁与打开的文件表条目相关联。这意味着重复的文件描述符(例如通过fork(2)或dup(2)创建的描述符)引用相同的锁定,可以使用任何这些描述符修改或释放此锁定。此外,锁定是通过任何这些重复描述符上的显式LOCK_UN操作或关闭所有这些描述符时释放的。

此外,flock锁定不会“堆叠”,因此如果您尝试获取已经持有的锁,则flock调用是一个noop,立即返回而不阻止并且不以任何方式更改锁定状态。

由于进程中的线程共享文件描述符,因此可以从不同的线程多次flock文件,它不会阻止,因为锁已经保持。

另外从flock(2)的注释中:

flock()和fcntl(2)锁与forked进程和dup(2)的语义不同。在实现使用fcntl(2)的flock()的系统上,flock()的语义将与本手册页面所述的不同。


克里斯,那不是真的。如果你查看我的 lsof 输出,你会发现每个线程的文件描述符都是唯一的。 - user1235176
3

进程内的线程共享文件描述符。 参见https://dev59.com/CW025IYBdhLWcg3wFxya 编辑

我认为,我们大多数编写过多线程程序的人都写过有效代码,在这些代码中,文件描述符在线程之间是共享的。
- Bob Stine
@user1235176 或许我漏掉了什么,但是我假设你看到不同的文件描述符,因为你为每个线程单独创建它们?我没有看到 fd 被传递,但我可能漏掉了某些细节。 - Petr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接