Статьи

Python Concurrency: введение в потоки

Python имеет ряд различных конструкций параллелизма, таких как потоки, очереди и многопроцессорность. Поточный модуль раньше был основным способом выполнения параллелизма. К сожалению, потоки в Python строго ограничены глобальной блокировкой интерпретатора (GIL), которая заставляет все ваши потоки работать на одном ядре. Если вы хотите запустить вашу программу, используя все ядра вашего процессора, вы должны использовать многопроцессорный модуль. Темы не бесполезны, хотя. Если у вас мощный ЦП и вы не собираетесь раскручивать слишком много потоков, то использование модуля потоков может быть вам правильным.


Начиная

Мы начнем с простого примера, который просто демонстрирует работу потоков. Мы создадим подкласс класса Thread и выведем его имя на стандартный вывод. Давайте получим кодирование!

import random
import time
 
from threading import Thread
 
########################################################################
class MyThread(Thread):
    """
    A threading example
    """
 
    #----------------------------------------------------------------------
    def __init__(self):
        """Initialize the thread"""
        Thread.__init__(self, name)
        self.name = name
        self.start()
 
    #----------------------------------------------------------------------
    def run(self):
        """Run the thread"""
        amount = random.randint(3, 15)
        time.sleep(amount)
        msg = "%s has finished!" % self.name
        print(msg)
 
#----------------------------------------------------------------------
def create_threads():
    """
    Create a group of threads
    """
    for i in range(5):
        name = "Thread #%s" % (i+1)
        my_thread = MyThread(name=name)
 
if __name__ == "__main__":
    create_threads()

В приведенном выше коде мы импортируем случайный модуль Python, модуль времени и импортируем класс Thread из модуля Threading. Затем мы создаем подкласс Thread и переопределяем его метод __init__, чтобы принять аргумент, который мы помечаем как «имя». Чтобы запустить поток, вы должны вызвать его метод start () , поэтому мы делаем это в конце init. Когда вы запускаете поток, он автоматически вызывает метод run . Мы переопределили его метод запуска, чтобы он выбирал случайное количество времени для сна. The random.randintпример здесь заставит Python случайным образом выбрать число из 3-15. Затем мы заставляем поток спать, количество секунд, которое мы случайно выбрали, чтобы смоделировать, что он действительно что-то делает. Наконец, мы выводим имя потока, чтобы пользователь знал, что поток завершен.

Функция create_threads создаст 5 потоков, дав каждому из них уникальное имя. Если вы запустите этот код, вы должны увидеть что-то вроде этого:

Thread #2 has finished!
Thread #1 has finished!
Thread #3 has finished!
Thread #4 has finished!
Thread #5 has finished!

The order of the output will be different each time. Try running the code a few times to see the order change.


Writing a Threaded Downloader

The previous example wasn’t very useful other than as a tool to explain how threads work. So in this example, we will create a Thread class that can download files from the internet. The U.S. Internal Revenue Service has lots of PDF forms that it has its citizens use for taxes. We will use this free resource for our demo. Here’s the code:

import os
import urllib2
 
from threading import Thread
 
########################################################################
class DownloadThread(Thread):
    """
    A threading example that can download a file
    """
 
    #----------------------------------------------------------------------
    def __init__(self, url, name):
        """Initialize the thread"""
        Thread.__init__(self)
        self.name = name
        self.url = url
 
    #----------------------------------------------------------------------
    def run(self):
        """Run the thread"""
        handle = urllib2.urlopen(self.url)
        fname = os.path.basename(self.url)
        with open(fname, "wb") as f_handler:
            while True:
                chunk = handle.read(1024)
                if not chunk:
                    break
                f_handler.write(chunk)
        msg = "%s has finished downloading %s!" % (self.name,
                                                   self.url)
        print(msg)
 
#----------------------------------------------------------------------
def main(urls):
    """
    Run the program
    """
    for item, url in enumerate(urls):
        name = "Thread %s" % (item+1)
        thread = DownloadThread(url, name)
        thread.start()
 
if __name__ == "__main__":
    urls = ["http://www.irs.gov/pub/irs-pdf/f1040.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040a.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040ez.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040es.pdf",
            "http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"]
    main(urls)

This is basically a complete rewrite of the first script. In this one we import the os and urllib2 modules as well as the threading module. We will be using urllib2 to do the actual downloading inside the thread class. The os module is used to extracting the name of the file we’re downloading so we can use it to create a file with the same name on our machine. In the DownloadThread class, we set up the __init__ to accept a url and a name for the thread. In the run method, we open up the url, extract the filename and then use that filename for naming / creating the file on disk. Then we use a while loop to download the file a byte at a time and write it to disk. Once the file is finished saving, we print out the name of the thread and which url has finished downloading.


Wrapping Up

Now you know how to use threads both in a theory and in a practical way. Threads are especially useful when you are creating a user interface and you want to keep your interface usable. Without threads, the user interface would become unresponsive and would appear to hang while you did a large file download or a big query against a database. To keep that from happening, you do the long running processes in threads and then communicate back to your interface when you are done.


Related Reading