Concurrency dengan Python - Kumpulan Untaian

Misalkan kita harus membuat sejumlah besar utas untuk tugas multithread kita. Ini akan menjadi paling mahal secara komputasi karena mungkin ada banyak masalah kinerja, karena terlalu banyak utas. Masalah utama bisa jadi pada throughput yang semakin terbatas. Kita dapat mengatasi masalah ini dengan membuat kumpulan utas. Kumpulan utas dapat didefinisikan sebagai grup utas yang sudah dibuat sebelumnya dan utas diam, yang siap untuk diberi pekerjaan. Membuat kumpulan utas lebih disukai daripada membuat contoh utas baru untuk setiap tugas ketika kita perlu melakukan banyak tugas. Kumpulan utas dapat mengelola eksekusi bersamaan dari sejumlah besar utas sebagai berikut -

  • Jika utas di kumpulan utas menyelesaikan eksekusinya, utas itu dapat digunakan kembali.

  • Jika utas dihentikan, utas lain akan dibuat untuk menggantikan utas itu.

Modul Python - Concurrent.futures

Pustaka standar Python menyertakan concurrent.futuresmodul. Modul ini ditambahkan dengan Python 3.2 untuk menyediakan antarmuka tingkat tinggi bagi pengembang untuk meluncurkan tugas-tugas asinkron. Ini adalah lapisan abstraksi di atas modul threading dan multiprocessing Python untuk menyediakan antarmuka untuk menjalankan tugas menggunakan kumpulan utas atau proses.

Di bagian selanjutnya, kita akan belajar tentang kelas-kelas yang berbeda dari modul concurrent.futures.

Kelas Pelaksana

Executoradalah kelas abstrak dari concurrent.futuresModul Python. Itu tidak dapat digunakan secara langsung dan kita perlu menggunakan salah satu dari subclass konkret berikut -

  • ThreadPoolExecutor
  • ProcessPoolExecutor

ThreadPoolExecutor - Subclass Beton

Ini adalah salah satu subclass konkret dari kelas Executor. Subclass menggunakan multi-threading dan kami mendapatkan kumpulan utas untuk mengirimkan tugas. Pangkalan ini menetapkan tugas ke utas yang tersedia dan menjadwalkannya untuk dijalankan.

Bagaimana cara membuat ThreadPoolExecutor?

Dengan bantuan dari concurrent.futures modul dan subkelas betonnya Executor, kita dapat dengan mudah membuat kumpulan utas. Untuk ini, kita perlu membangun fileThreadPoolExecutordengan jumlah utas yang kita inginkan di kumpulan. Secara default, jumlahnya adalah 5. Kemudian kita bisa mengirimkan tugas ke kumpulan thread. Ketika kitasubmit() tugas, kami kembali a Future. Objek Future memiliki metode yang disebutdone(), yang memberi tahu jika masa depan telah diselesaikan. Dengan ini, nilai telah ditetapkan untuk objek tertentu di masa mendatang. Saat tugas selesai, pelaksana kumpulan thread menetapkan nilai ke objek masa depan.

Contoh

from concurrent.futures import ThreadPoolExecutor
from time import sleep
def task(message):
   sleep(2)
   return message

def main():
   executor = ThreadPoolExecutor(5)
   future = executor.submit(task, ("Completed"))
   print(future.done())
   sleep(2)
   print(future.done())
   print(future.result())
if __name__ == '__main__':
main()

Keluaran

False
True
Completed

Dalam contoh di atas, a ThreadPoolExecutortelah dibuat dengan 5 utas. Kemudian tugas, yang akan menunggu selama 2 detik sebelum memberikan pesan, dikirimkan ke pelaksana kumpulan thread. Dilihat dari keluarannya, tugas tidak selesai sampai 2 detik, begitu juga panggilan pertama kedone()akan mengembalikan False. Setelah 2 detik, tugas selesai dan kita mendapatkan hasil di masa mendatang dengan memanggilresult() metode di atasnya.

Membuat instance ThreadPoolExecutor - Pengelola Konteks

Cara lain untuk membuat contoh ThreadPoolExecutoradalah dengan bantuan manajer konteks. Cara kerjanya mirip dengan metode yang digunakan dalam contoh di atas. Keuntungan utama menggunakan pengelola konteks adalah tampilannya bagus secara sintaksis. Instansiasi dapat dilakukan dengan bantuan kode berikut -

with ThreadPoolExecutor(max_workers = 5) as executor

Contoh

Contoh berikut dipinjam dari dokumen Python. Dalam contoh ini, pertama-tamaconcurrent.futuresmodul harus diimpor. Kemudian sebuah fungsi bernamaload_url()dibuat yang akan memuat url yang diminta. Fungsi kemudian membuatThreadPoolExecutordengan 5 utas di kolam. ItuThreadPoolExecutortelah digunakan sebagai manajer konteks. Kita bisa mendapatkan hasil masa depan dengan memanggilresult() metode di atasnya.

import concurrent.futures
import urllib.request

URLS = ['http://www.foxnews.com/',
   'http://www.cnn.com/',
   'http://europe.wsj.com/',
   'http://www.bbc.co.uk/',
   'http://some-made-up-domain.com/']

def load_url(url, timeout):
   with urllib.request.urlopen(url, timeout = timeout) as conn:
   return conn.read()

with concurrent.futures.ThreadPoolExecutor(max_workers = 5) as executor:

   future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
   for future in concurrent.futures.as_completed(future_to_url):
   url = future_to_url[future]
   try:
      data = future.result()
   except Exception as exc:
      print('%r generated an exception: %s' % (url, exc))
   else:
      print('%r page is %d bytes' % (url, len(data)))

Keluaran

Berikut akan menjadi output dari skrip Python di atas -

'http://some-made-up-domain.com/' generated an exception: <urlopen error [Errno 11004] getaddrinfo failed>
'http://www.foxnews.com/' page is 229313 bytes
'http://www.cnn.com/' page is 168933 bytes
'http://www.bbc.co.uk/' page is 283893 bytes
'http://europe.wsj.com/' page is 938109 bytes

Penggunaan fungsi Executor.map ()

Python map()fungsi banyak digunakan dalam sejumlah tugas. Salah satu tugas tersebut adalah menerapkan fungsi tertentu ke setiap elemen dalam iterable. Demikian pula, kita dapat memetakan semua elemen iterator ke suatu fungsi dan mengirimkannya sebagai tugas independen ke luarThreadPoolExecutor. Pertimbangkan contoh skrip Python berikut untuk memahami cara kerja fungsinya.

Contoh

Dalam contoh di bawah ini, fungsi peta digunakan untuk menerapkan square() berfungsi untuk setiap nilai dalam larik nilai.

from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import as_completed
values = [2,3,4,5]
def square(n):
   return n * n
def main():
   with ThreadPoolExecutor(max_workers = 3) as executor:
      results = executor.map(square, values)
for result in results:
      print(result)
if __name__ == '__main__':
   main()

Keluaran

Skrip Python di atas menghasilkan output berikut -

4
9
16
25