Что может замедлять мою программу, когда я использую многопоточность?

Я пишу программу, которая загружает данные с веб-сайта (eve-central.com). Он возвращает xml, когда я отправляю запрос GET с некоторыми параметрами. Проблема в том, что мне нужно сделать около 7080 таких запросов, потому что я не могу указать параметр typeid более одного раза.

def get_data_eve_central(typeids, system, hours, minq=1, thread_count=1):
    import xmltodict, urllib3
    pool = urllib3.HTTPConnectionPool('api.eve-central.com')
    for typeid in typeids:
        r = pool.request('GET', '/api/quicklook', fields={'typeid': typeid, 'usesystem': system, 'sethours': hours, 'setminQ': minq})
        answer = xmltodict.parse(r.data)

Это было очень медленно, когда я только что подключился к веб-сайту и сделал все запросы, поэтому я решил использовать несколько потоков одновременно (я читал, что если процесс включает в себя много ожидания (запросы ввода-вывода, HTTP-запросы), он можно значительно ускорить с помощью многопоточности). Я переписал его, используя несколько потоков, но он почему-то не стал быстрее (на самом деле немного медленнее). Вот код, переписанный с использованием многопоточности:

def get_data_eve_central(all_typeids, system, hours, minq=1, thread_count=1):

    if thread_count > len(all_typeids): raise NameError('TooManyThreads')

    def requester(typeids):
        pool = urllib3.HTTPConnectionPool('api.eve-central.com')
        for typeid in typeids:
            r = pool.request('GET', '/api/quicklook', fields={'typeid': typeid, 'usesystem': system, 'sethours': hours, 'setminQ': minq})
            answer = xmltodict.parse(r.data)['evec_api']['quicklook']
            answers.append(answer)

    def chunkify(items, quantity):
        chunk_len = len(items) // quantity
        rest_count = len(items) % quantity
        chunks = []
        for i in range(quantity):
            chunk = items[:chunk_len]
            items = items[chunk_len:]
            if rest_count and items:
                chunk.append(items.pop(0))
                rest_count -= 1
            chunks.append(chunk)
        return chunks

    t = time.clock()
    threads = []
    answers = []
    for typeids in chunkify(all_typeids, thread_count):
        threads.append(threading.Thread(target=requester, args=[typeids]))
        threads[-1].start()
        threads[-1].join()

    print(time.clock()-t)
    return answers

Что я делаю, так это делю все typeids на столько фрагментов, сколько потоков я хочу использовать, и создаю поток для каждого фрагмента для его обработки. Вопрос: что может его замедлить? (прошу прощения за мой плохой английский)


person Ilya Peterov    schedule 02.03.2015    source источник


Ответы (1)


Python имеет глобальную блокировку интерпретатора. Это может быть ваша проблема. На самом деле Python не может сделать это по-настоящему параллельно. Вы можете подумать о переходе на другие языки или о том, чтобы остаться с Python, но использовать параллелизм на основе процессов для решения своей задачи. Вот хорошая презентация Внутри Python GIL

person Vadim    schedule 02.03.2015
comment
Я тоже так думаю, но не вижу, где мои темы используют подобный ресурс. Они используют список ответов, да, но я попытался удалить его, и он был таким же медленным, поэтому я думаю, что это не проблема. - person Ilya Peterov; 02.03.2015
comment
Взгляните на красивую презентацию dabeaz.com/python/GIL.pdf Внутри Питон ГИЛ. После этого, я думаю, вы точно узнаете, почему GIL создает вам проблемы. - person Vadim; 02.03.2015
comment
Прочитал презентацию (кстати, отличная, спасибо), но так и не понял, в чем проблема в моем конкретном случае. Операция, которая занимает больше всего времени, это ожидание ответа сервера, но это операция ввода-вывода и она (как я понял из презентации) должна освобождать GIL во время ожидания, что она (судя по времени выполнения программы ) не делает. - person Ilya Peterov; 02.03.2015
comment
Я использую 'for', чтобы дать потокам свои задачи, я думаю, что неплохо попробовать использовать очереди, как было рекомендовано здесь - person Ilya Peterov; 02.03.2015
comment
Я переписал свою программу с использованием очередей, и она действительно стала намного быстрее. Спасибо! - person Ilya Peterov; 02.03.2015