LINUX.ORG.RU

многопоточность в python скрипте: как устранить блокирование

 , , ,


1

1

Имеется Ubuntu 16.04.6 LTS с python-2.7.12. В питоне не эксперт, но вынужден поддерживать переданный по наследству код. Вот фрагмент:

from threading import Thread
...

class Shell(cmd.Cmd):
    ...
    def do_start(self, line):
        threads = []

        t = Thread(target=traffic(line, arg1, arg2, arg3))
        threads.append(t)
        t.start()
        t.join()
...

if __name__ == '__main__':
    global config
    global args

    args = parse_args()
    config = configparser.ConfigParser()
    config.read(args.FILE)

    s = Shell()
    ...

То есть запускает небольшой command-line shell, где набираются определенные команды и они выполняются. Это работает, но питоновский CLI блокируется когда стартует «поток».

Погуглил и подумал, что добавление t.setDaemon(True) (до t.start() или после) должно помочь. Однако должного эффекта не возымело. Также пробовал убрать t.join() — аналогично.

Что я делаю не так?


В чем смысл всей этой логики, что вообще сделать хотел? Ты в методе создаешь поток и ждешь его выполнения. Это ничем не отличается от однопоточного кода кроме оверхеда на создание нового потока.

Shtsh ★★★★ ()
Ответ на: комментарий от Shtsh

В чем смысл всей этой логики, что вообще сделать хотел? Ты в методе создаешь поток и ждешь его выполнения. Это ничем не отличается от однопоточного кода кроме оверхеда на создание нового потока.

Это не мой код, достался в наследство, переписывать нет времени и знаний :(

Вообще этот скрипт используя python API от trex (трафикогенератор), подключается к машине на которой установлен TRex, и запускает трафик (через API передается куча параметров и пр.). Таких машин много, поэтому хотелось бы запускать по необходимости, а не ждать когда текущий тест завершится.

cruz7 ()
Ответ на: комментарий от cruz7

Проблема исключительно в твоем коде. В голом коде где есть блокирующие вызовы все работает.

def func(name):
     print name, 'start'
     sleep(5)
     print name, 'end'

t1 = Thread(target=func, args=(1,))
t2 = Thread(target=func, args=(2,))

Output:
1 start
2 start
1 end
2 end

Смотри свою traffic, которую ты, кстати, вызываешь неправильно, есть ли там системные вызовы, которые блокируют поток. Если нету - то «параллельности» не будет.

Shtsh ★★★★ ()
Ответ на: комментарий от Shtsh

Объясню теорию: в питоне все потоки выполняются последовательно из-за global interpreter lock для упрощения менеджмента памяти. Единственная рабочая возможность параллельной работе, освобождать GIL в сишном коде, если ты там не создаешь новые питоновские объекты.

На практике, это обычно происходит при вызове системных блокирущих вызовов (например, связанных c IO или каких-нибудь sleep). Поэтому можно делать сетевые запросы в отдельных потоках и это будет выполняться одновременно.

Если нужна параллельность, то проще всего использовать https://docs.python.org/2/library/multiprocessing.html

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

Но что-то мне подсказывает, что проблема именно в логике твоего скрипта. Просто очень тяжело оценить, что не так по тому, что ты выложил, так как у тебя там в принципе непонятно зачем добавление потоков в список, и зачем ты там ожидаешь завершение выполнения потока.

Shtsh ★★★★ ()
Ответ на: комментарий от Shtsh

Смотри свою traffic, которую ты, кстати, вызываешь неправильно,

Ага, наверное в этом собака порылась:

        threads = []

        t = Thread(target=traffic, args=(line, arg1, arg2, arg3))
        threads.append(t)
        t.start()

есть ли там системные вызовы, которые блокируют поток.

Да, в traffic() есть вызовы блокирующие поток.

cruz7 ()
Последнее исправление: cruz7 (всего исправлений: 1)
Ответ на: комментарий от Shtsh

Спасибо за ликбез!

Но не думаю, что в твоем случае это важно, раз ты просто запускаешь другое приложение.

Я не запускаю другое приложение локально. API позволяет через RPC вызовы обращаться к trex-серверу на др. машинах и запускать трафикогенератор на них.

Но что-то мне подсказывает, что проблема именно в логике твоего скрипта. Просто очень тяжело оценить, что не так по тому, что ты выложил, так как у тебя там в принципе непонятно зачем добавление потоков в список, и зачем ты там ожидаешь завершение выполнения потока.

Этот код не мой, я только добавил t.setDaemon(True).

cruz7 ()
Ответ на: комментарий от cruz7

Если со скобочками все хорошо, тогда это работает так.

t = Thread(target=traffic(line, arg1, arg2, arg3))
1. выполняется traffic(line, arg1, arg2, arg3)
2. выглядит так, что эта функция возвращает None
3. Создается объект Thread(target=None)

t.start()
Создается процесс, который выполяет None, то есть ничего не вызывается и он сразу же завершается
Если я правильно понял, твоя функция работает долго, то есть ты в основном потоке ее сразу же выполняешь (шаг 1) даже до создания объекта Thread.

Shtsh ★★★★ ()
Ответ на: комментарий от menangen

От asyncio вообще никакого профита не будет в данном случае. А в скрипте это приведет с значительному усложнению логики и более тяжелой поддержке.

И это я не касаюсь того, что практически все сторонние async библиотеки - лютое говно, которое вместо неблокирующих операций тупо выполняют все в отдельном потоке.

Shtsh ★★★★ ()
Ответ на: комментарий от Shtsh

А какой синтакс правильный:

t = Thread(target=traffic(line, arg1, arg2, arg3))

или

t = Thread(target=traffic, args=(line, arg1, arg2, arg3))

В обоих случаях Python не ругается. Но в первом случае traffic() выполняется и блокирует I/O командного шелла (и трафик реально генерируется), во втором случае выполняется как бы в фоне, CLI доступна и я могу выполнить traffic() еще раз (т.е. запустить трафикогенератор на другой машине).

cruz7 ()
Ответ на: комментарий от cruz7

Оба синтаксиса правильные, но делают разное: в первом случае у тебя функция вызывается до создания Thread в главном потоке, во втором - после вызова .start() в новом потоке.

Shtsh ★★★★ ()