Дескриптор файла открыт для родительского процесса, но закрыт для каждого дочернего процесса

Я написал этот небольшой пример:

import multiprocessing
from functools import partial

def foo(x, fp):
    print str(x) + " "+ str(fp.closed)
    return

def main():
    with open("test.txt", 'r') as file:
        pool = multiprocessing.Pool(multiprocessing.cpu_count())
        partial_foo = partial(foo, fp=file)
        print file.closed
        pool.map(partial_foo, [1,2,3,4])
        pool.close()
        pool.join()
        print file.closed
        print "done"

if __name__=='__main__':
    main()

Который будет печатать:

False
2 True
3 True 
1 True
4 True
False
done

Мой вопрос будет заключаться в том, почему дескрипторы файлов закрыты для дочернего процесса и как я могу держать их открытыми, чтобы каждый процесс мог работать с файлом?

Раз уж в комментариях спросили:

$ uname -a && python2.7 -V
Linux X220 3.17.6-1-ARCH #1 SMP PREEMPT Sun Dec 7 23:43:32 UTC 2014 x86_64 GNU/Linux
Python 2.7.9

person ap0    schedule 17.12.2014    source источник
comment
Какую ОС вы используете? Какая версия питона?   -  person dano    schedule 17.12.2014
comment
@дано, линукс. Arch Linux, если быть точным, работает под управлением Python 2.7.   -  person ap0    schedule 17.12.2014
comment
Связанный вопрос: stackoverflow .com/questions/1075443/. В общем, вы, вероятно, не хотите делать это, хотя есть способы, специфичные для платформы, которые вы можете сделать. Что вы на самом деле хотите, чтобы каждый ребенок делал с fd?   -  person dano    schedule 17.12.2014
comment
@dano, я пытаюсь объяснить это вкратце. Итерируемый список, предоставленный функции pool.map, представляет собой ряд блоков/кластеров, которые я хочу, чтобы функция анализировала. Поэтому я подумал, что этот способ будет лучше, чем открывать и закрывать файл при каждом запущенном процессе.   -  person ap0    schedule 17.12.2014
comment
@dano, как многопроцессорность может предотвратить дублирование файловых дескрипторов в неявных fork?   -  person Reut Sharabani    schedule 17.12.2014
comment
Я не был уверен, что нужно удалить свой вопрос или пометить его как дубликат, как показал мне Дано. Так что я просто отметил это. Это может быть удалено, если это будет лучше. Буду искать другое решение.   -  person ap0    schedule 17.12.2014
comment
@ReutSharabani В Python 3.4+ вы можете использовать контекст для использования метода, отличного от fork, для создания дочерних процессов. До этого все, что вы можете сделать, это попытаться сохранить fd вне глобального состояния или явно закрыть открытые fd в дочернем процессе после того, как произошло дублирование.   -  person dano    schedule 17.12.2014
comment
@ ap0 Ваш лучший вариант, вероятно, просто явно открыть файл в каждом дочернем элементе.   -  person dano    schedule 17.12.2014
comment
@dano, я думал об использовании Queue. Разве это не было бы лучше? Я позволяю родительскому процессу читать из файла, загружаю его в очередь и позволяю дочернему процессу работать с ним. Или, по-вашему, лучше открывать и закрывать файл в каждом ребенке?   -  person ap0    schedule 17.12.2014
comment
@ ap0 Да, ты тоже можешь это сделать. Вы оплачиваете стоимость IPC за отправку данных из файла между процессами, делающими это таким образом, но это может оказаться дешевле, чем когда все дочерние процессы пытаются одновременно читать разные части одного и того же файла.   -  person dano    schedule 17.12.2014
comment
@dano, о, значит, нет проблем, когда 4 дескриптора открыты для одного и того же файла?   -  person ap0    schedule 17.12.2014
comment
@ ap0 Это будет работать, но может работать не очень хорошо, потому что все процессы будут пытаться читать с диска одновременно. Ваш жесткий диск может считывать только из одного места одновременно, поэтому в конечном итоге он будет переходить туда и обратно между разными местами диска для чтения данных для каждого процесса. Это может оказаться медленнее, чем просто последовательное чтение файла один раз, а затем отправка этих данных дочерним элементам через Queues. Я бы, вероятно, предпочел открывать файл в каждом дочернем элементе только в том случае, если вы отправляете огромные объемы данных через очереди, поскольку стоимость IPC будет очень высокой.   -  person dano    schedule 17.12.2014
comment
Что я хочу сделать, так это прочитать 512 байт (или больше, в зависимости от размера кластера этого образа жесткого диска) и обработать это. Какова будет ваша окончательная рекомендация? Очереди или файловый обработчик для каждого дочернего процесса?   -  person ap0    schedule 17.12.2014


Ответы (1)


Это связано с передачей файла в качестве аргумента. Изменил fp в файл

import multiprocessing
from functools import partial

def foo(x, fp):
    print str(x) + " "+ str(file.closed)
    return

if __name__=='__main__':
    with open("test.txt", 'r') as file:
        pool = multiprocessing.Pool(multiprocessing.cpu_count())
        partial_foo = partial(foo, fp=file)
        print file.closed
        pool.map(partial_foo, [1,2,3,4])
        pool.close()
        pool.join()
        print file.closed
        print "done"

выход

False
1 False
2 False
3 False
4 False
False
done
person Robert Jacobs    schedule 17.12.2014
comment
Какая? Почему это работает? Разве я не должен получить сообщение об ошибке, говорящее, что file в функции foo неизвестно? Но вы правы, это работает. - person ap0; 17.12.2014
comment
Но где? По крайней мере, я не создал глобальный файловый объект. Или я? Извините, если этот вопрос глупый. - person ap0; 17.12.2014
comment
Это связано с тем, что в Linux fork используется для порождения дочерних процессов в Pool, поэтому объект file, определенный внутри блока if __name__ == "__main__", наследуется в каждом дочернем процессе. Этот код сломается, если вы откроете файл после создания экземпляра Pool. - person dano; 17.12.2014
comment
Этот ответ не имеет смысла. Насколько я могу судить, вы можете удалить fp... Все, что вы сделали, это взяли file из прицела main. - person Reut Sharabani; 17.12.2014
comment
Другими словами, вы фактически не отправляете open fd между процессом в вызове pool.map. Открытый fd наследуется от глобального состояния каждым дочерним элементом, когда fork вызывается в вызове multiprocessing.Pool(...), что означает, что вы можете получить к нему доступ в рабочем процессе. - person dano; 17.12.2014
comment
Ну, проблема в том, что я непреднамеренно сделал файловый объект глобальным. Решения Roberss работают, но не в том случае, если я использую их в своей реальной программе, где файловый объект создается не глобально. Я не думал об этом. - person ap0; 17.12.2014