在多线程环境中,什么可能导致"bad file descriptor"?



这个问题在某种程度上类似于Bad文件描述符,但根本不一样。我知道这是";糟糕的问题";("可能太本地化了"),但我想不通,现在我没有任何想法了


简介

我有一个管理器线程,它启动其他75个线程。这些线程中的每一个都做了很多事情,所以我只描述相关的线程。

请注意:如果我只启动几个线程,例如3、5或10,则不会出现此错误!这让我觉得,这是一些多线程问题,但事实并非如此。。您将在下一节中看到原因。

因此,在以下两种情况下,有时我会收到此错误Bad file descriptor:


情况1

错误出现在TinyXML

有一个xml文件,所有线程都需要它。所有这些线程都使用TinyXML来解析文件。所有这些线程都使用此文件只读(我知道这是可以优化的,但不管怎样)。

因此,导致Bad file descriptor错误的代码是:

// ...
// NOTE: this is LOCAL, other threads do NOT have access to it
TiXmlDocument   doc;
doc.LoadFile( filename );
// and here's the LoadFile:
bool TiXmlDocument::LoadFile( const char* _filename, TiXmlEncoding encoding )
{
//...
FILE* file = fopen( value.c_str (), "rb" ); 
if ( file )
{
// this IS executed, so file is NOT NULL for sure
bool result = LoadFile( file, encoding );
//...
}
//...
}
bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding )
{
// ...
long length = 0;
fseek( file, 0, SEEK_END );
// from the code above, we are SURE that file is NOT NULL, it's valid, but
length = ftell( file ); // RETURNS -1 with errno: 9 (BAD FILE DESCRIPTOR)
// how is this possible, as "file" is not NULL and it appears to be valid?
// ...
}

情况2

这有点复杂。我已经删除了对返回值的检查,但我的真实代码中有它们,所以这不是的问题

int hFileR = open( sAlarmFileName.c_str(), O_CREAT | O_RDONLY, 
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH );
// hFileR is > 0 for sure, so success 
flock( hFileR, LOCK_EX ) /* the result is > 0 for sure, so success*/ 
// read the file into a string
while( (nRes = read(hFileR, BUFF, MAX_RW_BUFF_SIZE)) > 0 ) // ...
//Write new data to file: reopen/create file - write and truncate mode
int hFileW = open( sAlarmFileName.c_str(), 
O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | 
S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH );
// hFileW is > 0 for sure, so success
do
{
int nWrtRes = write( hFileW, 
szText + nBytesWritten, nSize - nBytesWritten ); 
// nWrtRes is always >= 0, so success
nBytesWritten +=  nWrtRes;
}
while( nSize > nBytesWritten );
close( hFileW );    // this one is successful too
if( flock(hFileR, LOCK_UN) == -1 )
{
// THIS FAILS and executes _Exit( FAILURE );
}
if( close( hFileR ) < 0 )
{
// if the previous one do not fail, this one is successful too
}

很抱歉问了这么长的问题。有什么想法吗?

需要查找的一件事是两次关闭同一文件描述符的代码。

在单线程程序中,这是一个无害的编程错误,因为第二个close()除了返回EBADF之外什么都不做,而且很多代码无论如何都不需要检查close()的返回值。然而,在多线程程序中,在对close()的两次调用之间,可以在另一个线程中分配已关闭描述符的描述符编号,因此第二个close()将关闭来自另一线程的不相关套接字。进一步读取和写入其他线程的描述符将导致"坏文件描述符"错误。

关于理解文件描述符的一些词语:

文件是全局资源。为了处理这种情况,使用了一个(进程)全局索引:称为文件描述符的整数值。如果线程打开一个文件,则该打开的文件将由索引引用。此索引对于进程是唯一的(而不是对于线程)。如果文件关闭,则不再使用文件描述符(整数索引),进程(及其任何线程)都可以重用该描述符。

示例:

进程中的任何线程对open()的第一次调用都可能返回3,第二次调用可能返回4。

如果3关闭,则对open()的第三次调用可以再次返回3。

如果第一次调用是由线程1完成的,第二次调用是线程2完成的,而第三次调用是通过线程3完成的,那么很容易理解线程1不应该再次关闭其文件描述符,因为3的值可能已经被回收并由线程3使用,线程3将尝试访问无效的文件描述符,就像它可能已经被线程1对close()的第二次(错误)调用关闭一样。好吗?;-)

尝试设置一些示例代码,并检查/记录调用open()返回的整数值,这些整数值被分配为文件描述符,以了解它的工作原理。

注意

这也可能指stdinstdoutstderr,即"预定义"的文件描述符012。在最近的Linux中,关闭stdin,然后调用int fd = open("myfoofile.bar", ...),很可能会将0作为文件描述符fd返回。无论如何,内核或glibc都不能像预期的那样处理这样的0。例如,使用lseek(fd, ...)可能会出现模糊错误。试试看!;->>

如果应用程序是多线程的,如果某个线程关闭了文件,而另一个线程仍试图访问它,则可能会发生这种情况。

(因为文件描述符,如地址空间,是全局的,对进程的所有线程都是通用的)

您可以使用strace来了解系统调用是如何完成的。

相关内容

  • 没有找到相关文章

最新更新