не может распараллеливать (потоки) соединения telnet с python

У меня есть код, который будет подключаться к нескольким маршрутизаторам с помощью telentlib, запускать в них некоторый код и, наконец, записывать вывод в файл. Он работает гладко.

Однако, когда мне нужно было получить доступ к большому количеству маршрутизаторов (+50), задача отнимала много времени (код выполнялся последовательно, по одному маршрутизатору за раз). Я подумал тогда о реализации потоков, чтобы ускорить процесс.

Это в значительной степени код (просто его фрагмент):

# We import the expect library for python
import telnetlib
import sys
import csv
import time
import threading

# --- Timers
TimeLogin = 10
TelnetWriteTimeout = 1
TelnetReadTimeout = 2
# --- CSV
FileCsv = "/home/lucas/Documents/script/file.csv"
# --- Extras
cr="\n"

# variables
IP = "A.B.C.D"
User = ["user","password"]
CliLogin = "telnet " + IP
Prompt = ["root@host.*]#"]
PromptLogin = ["login:"]
PromptLogout = ["logout"]
PromptPass = ["Password:"]
CliLine = "ls -l"

class MiThread(threading.Thread):  
    def __init__(self,num,datos):  
        threading.Thread.__init__(self)
        self.num = num
        self.datos = datos
        self.systemIP = self.datos[0]
        self.tn = telnetlib.Telnet(IP)
        self.tn.timeout = TimeLogin

        # File declaration
        self.FileOutGen = self.systemIP + "_commands"
        self.FileOutSend = self.systemIP + "_output"
        self.FileOutRx = self.systemIP + "_rx"      
        self.fg = open(self.FileOutGen,"w")
        self.fr = open(self.FileOutRx,"a")
        self.fs = open(self.FileOutSend,"w")

    def run(self):  
        print "Soy el hilo", self.num
        self.telnetLogin()
        self.runLs()
        self.telnetLogout()

    def telnetLogin(self):      
        i=self.tn.expect(PromptLogin)
        print i
        if i:
            writeTelnet(User[0],TelnetWriteTimeout)
            j=self.tn.expect(PromptPass)
            print j
            if j:
                writeTelnet(User[1],TelnetWriteTimeout)

    def telnetLogout(self):
        i=self.tn.expect(Prompt)
        if i:
            writeTelnet("exit",TelnetWriteTimeout)
            j=self.tn.expect(PromptLogout)
            if j:
                print "Logged out OK from SAM"

    def runLs(self):
        writeLog("Prueba de Ls " + self.systemIP)
        self.writeCsv(self.systemIP,1)
        i=self.tn.expect(Prompt,TimeLogin)
        print i
        if i:
            # Prompt
            CliLine = "ls -l "
            writeTelnet(CliLine,TelnetWriteTimeout)

    def writeCsv(self,inText,lastIn):
        lock.acquire(1)
        if lastIn==0:
            fc.write(inText + ",")
        elif lastIn==1:
            fc.write(inText + "\n")
        lock.release()

def writeTelnet(inText, timer):
    tn.write(inText + cr)
    time.sleep(timer)

def writeLog(inText):
    print (inText)
    t.fs.write("\n" + inText)

def printConsole(inText):
    print (inText)

oFile = csv.reader(open(FileCsv,"r"), delimiter=",", quotechar="|")

lock = threading.Lock()
routers = list(oFile)
threads_list = []

for i in range(len(routers)):
    FileOutCsv = "00_log.csv"

    # creating output file
    fc = open(FileOutCsv,"a")

    # running routine
    t = MiThread(i,routers[i])
    threads_list.append(t)
    t.start()

... все работает хорошо, но выигрыша во времени нет, так как t.join() заставит поток завершиться до запуска следующего!

Факт сериализации потоков (имеется в виду использование t.join()) заставляет меня думать, что некоторые области памяти являются общими, потому что проблема возникает, когда я хочу их распараллелить (комментируя t.join()).

Что-то не так, что я делаю? Я могу предоставить дополнительную информацию, если это необходимо, но я действительно не знаю, что я делаю неправильно до сих пор ...


person Lucas Aimaretto    schedule 05.01.2015    source источник
comment
похоже, что ваши потоки блокируют друг друга при параллельной работе.   -  person Ashalynd    schedule 06.01.2015
comment
Я предполагаю, что вы используете некоторые глобальные переменные или telnet lib используют глобальные переменные. Можно попробовать использовать многопроцессорность вместо многопоточности... Интерфейс тот же.   -  person Michele d'Amico    schedule 06.01.2015
comment
Привет, @Michele, да, ты прав: я использую некоторые глобальные переменные (например, имя пользователя/пароль для входа в систему), поэтому я подумал, что с их использованием не возникнет проблем. Почему существует корреляция между распараллеливанием этих соединений и глобальными переменными? В любом случае я попробую многопроцессорность вместо многопоточности. Вопрос: Я тестирую этот код в Linux. Если я использую MP, легко ли его будет перенесено на Windows?   -  person Lucas Aimaretto    schedule 06.01.2015
comment
Привет @Ashalynd, да, я думаю, что они блокируют друг друга. Любая подсказка о том, как предотвратить это?   -  person Lucas Aimaretto    schedule 06.01.2015
comment
Привет @Micheled'Amico, прежде чем перейти к MP, я бы предпочел остаться с MT. Чтобы избежать этой проблемы, должен ли я поместить все глобальные переменные в определение класса? Я думаю, что это может сработать, но я не вижу в этом такого элегантного подхода...   -  person Lucas Aimaretto    schedule 06.01.2015
comment
@LucasAimaretto слишком много вопросов ... :) Прежде всего, если вы просто читаете глобальные переменные без изменения (то есть с помощью оператора global), это не может быть причиной. В любом случае, попробуйте многопроцессорность — это просто docs.python.org/2/library/multiprocessing.html и вы можете портировать его на Windows без каких-либо усилий. Если проблема связана с библиотекой telnet (например, проблема GIL wiki.python.org/moin/GlobalInterpreterLock) multiprocess исправит это. Я предполагаю, что проблема заключается в коде, который вы не опубликовали, и многопроцессорность может быть излишним подходом, но может быть полезно иметь рабочий код.   -  person Michele d'Amico    schedule 06.01.2015
comment
@LucasAimaretto Еще одна вещь. Да, вы можете поместить глобальные переменные в определение класса или, что лучше, передать его в конструктор. Но, возможно, это не основная причина: если вы разместите больше кода о своем методе, возможно, будет проще угадать причину: ваш опубликованный пример работает без каких-либо проблем и не дает никакой информации о том, где может быть проблема. Наконец я нашел пример, в котором telnetlib работает в контексте многопоточности stackoverflow.com/a/18378683/4101725, и теперь я вполне уверен, что ваша проблема в не опубликованном коде.   -  person Michele d'Amico    schedule 06.01.2015
comment
@Micheled'Amico, хорошо, думаю, я нашел ошибку. Я написал функцию (которой нет во фрагменте), которая записывала бы в телнет, который не был объявлен внутри класса, а только вне его. Каждый поток, желая записать в свое telnet-соединение, попытается его использовать. Как только я переместил функцию внутрь класса, пока все работало нормально. Спасибо за ваши советы! Они заставили меня задуматься о воспоминании, которым делятся. Вопрос: Если я хочу поделиться с вами всем полным кодом моей программы, должен ли я изменить исходный пост? Или мне начинать новую?   -  person Lucas Aimaretto    schedule 06.01.2015
comment
@LucasAimaretto Я думаю, что лучше всего изменить его: заголовок правильный, вы просто забыли добавить соответствующую часть кода.   -  person Michele d'Amico    schedule 06.01.2015
comment
@LucasAimaretto Будет лучше, если вы подадите ответ вместе с ответом вместо использования РЕШЕНО в вопросе. Я проголосую за это.   -  person Michele d'Amico    schedule 06.01.2015
comment
@Micheled'Amico, понял, но, простите меня за вопрос: я пытался отредактировать сообщение и не нашел способа загрузить файл с ответом, включая решение. Какой способ загрузки файлов является подходящим?   -  person Lucas Aimaretto    schedule 07.01.2015
comment
@LucasAimaretto Вы не должны публиковать файл. Просто напишите исходный неправильный код (лучше, если вы используете его упрощенную версию), а затем опубликуйте ответ, где вы исправляете свой код, и объясните, в чем проблема.   -  person Michele d'Amico    schedule 07.01.2015


Ответы (3)


Итак, немного покопавшись, ошибка была найдена.

Раньше функция writeTelnet() объявлялась вне класса. После того, как он был перемещен внутрь него и на него правильно ссылаются остальные (например: self.writeTelnet()), все работало, как и ожидалось.

Вот фрагмент нового кода:

class MiThread(threading.Thread):  
    def __init__(self,num,datos):  
        threading.Thread.__init__(self)
        self.num = num
        self.datos = datos
        self.systemIP = self.datos[0]
        self.tn = telnetlib.Telnet(IP)
        self.tn.timeout = TimeLogin

        # File declaration
        self.FileOutGen = self.systemIP + "_commands"
        self.FileOutSend = self.systemIP + "_output"
        self.FileOutRx = self.systemIP + "_rx"      
        self.fg = open(self.FileOutGen,"w")
        self.fr = open(self.FileOutRx,"a")
        self.fs = open(self.FileOutSend,"w")

    [ . . . ]

    def writeTelnet(self,inText, timer):
        self.tn.write(inText + ch_cr)
        time.sleep(timer)

oFile = csv.reader(open(FileCsv,"r"), delimiter=",", quotechar="|")
routers = list(oFile)

for i in range(len(routers)):   
    # creating output file
    fc = open(FileOutCsv,"a")

    # running routine
    t = MiThread(i,routers[i])
    t.start()

И теперь ясно (по крайней мере, для меня): поскольку разным потокам в какой-то момент нужно записать в свое телнет-соединение, им нужно однозначно его идентифицировать. Единственный способ, который я нашел, - включить функцию внутри класса.

Спасибо вам всем,

Лукас

person Lucas Aimaretto    schedule 07.01.2015

Исходя из прошлого опыта, вам нужно запускать все потоки и объединять их после того, как все они будут запущены.

thread_list = []
for i in range(routers):
    t = MiThread(i, routers[i])
    threadlist.append(t)
    t.start()

for i in thread_list:
    i.join()

Это запустит каждый поток, а затем дождется завершения всех потоков, прежде чем двигаться дальше.

person nerdyKnight    schedule 05.01.2015
comment
Привет @nerdyKnight, я попробовал твое предложение, и оно не решает проблему. Зависает снова навсегда. Действительно, мне кажется, что запуск всех потоков одновременно является проблемой, потому что, если я помещу t.join() после запуска потока, проблема исчезнет... - person Lucas Aimaretto; 06.01.2015
comment
Я думаю, что вы можете сгруппировать их. - person Abhishek Dave; 07.01.2015

Я думаю, что вы можете сгруппировать их. что-то вроде ниже. (не пробовал)

count=20
start=1
for i in range(len(routers)):
  if start == count :
    for _myth in range(1,i+1):
        thread_list[_myth].join()
    start+=1
FileOutCsv = "00_log.csv"

# creating output file
fc = open(FileOutCsv,"a")

# running routine
t = MiThread(i,routers[i])
threads_list.append(t)
t.start()
start+=1
person Abhishek Dave    schedule 07.01.2015