Penggunaan fungsi PARALELL pada PHP

Python adalah salah satu bahasa yang paling populer untuk pemrosesan data dan ilmu data secara umum. Ekosistem menyediakan banyak pustaka dan kerangka kerja yang memfasilitasi komputasi berkinerja tinggi. Melakukan pemrograman paralel dengan Python dapat membuktikan cukup rumit.

Dalam tutorial ini, kita akan mempelajari mengapa paralelisme sulit terutama dalam konteks Python, dan untuk itu, kita akan membahas hal-hal berikut:

  • Mengapa paralelisme sulit dengan Python (petunjuk: itu karena GIL—global interpreter lock).
  • Threads vs .Processes: Berbagai cara untuk mencapai paralelisme. Kapan menggunakan salah satu dari yang lain?
  • Paralel vs .Konkurensi: Mengapa dalam beberapa kasus kita dapat menerima konkurensi daripada paralelisme.
  • Membangun contoh sederhana namun praktis dengan menggunakan berbagai teknik yang dibahas.

Global Interpreter Lock

Global Interpreter Lock (GIL) adalah salah satu mata pelajaran paling kontroversial di dunia Python. Dalam CPython, implementasi Python yang paling populer, GIL adalah mutex yang membuat segala sesuatunya aman. GIL memudahkan untuk mengintegrasikan dengan pustaka eksternal yang tidak aman-thread, dan itu membuat kode non-paralel menjadi lebih cepat. Namun, ini membutuhkan biaya. Karena GIL, kami tidak dapat mencapai paralelisme sejati melalui multithreading. Pada dasarnya, dua utas asli yang berbeda dari proses yang sama tidak dapat menjalankan kode Python sekaligus.

Hal-hal tidak seburuk itu, dan inilah alasannya: hal-hal yang terjadi di luar wilayah GIL bebas untuk sejajar. Dalam kategori ini jatuh tugas yang berjalan lama seperti I/O dan, untungnya, perpustakaan seperti numpy.

Threads vs. Processes

Jadi Python tidak benar-benar multithread. Tapi apa itu trhread? Mari kita mundur selangkah dan melihat hal-hal dalam perspektif.

Proses adalah abstraksi sistem operasi dasar. Ini adalah program yang sedang dieksekusi—dengan kata lain, kode yang sedang berjalan. Beberapa proses selalu berjalan di komputer, dan mereka mengeksekusi secara paralel.

Suatu proses dapat memiliki beberapa utas. Mereka mengeksekusi kode yang sama milik proses induk. Idealnya, mereka berjalan secara paralel, tetapi tidak harus. Alasan mengapa proses tidak cukup adalah karena aplikasi harus responsif dan mendengarkan tindakan pengguna saat memperbarui tampilan dan menyimpan file.

Jika itu masih agak tidak jelas, berikut ini cheatsheet:

PROCESSES
THREADS
Proses tidak berbagi memori
Thread berbagi memori
Proses spawning/switching adalah mahal
Spawning/switching threads lebih murah
Processes membutuhkan lebih banyak sumber daya
Threads membutuhkan lebih sedikit sumber daya (kadang-kadang disebut proses ringan)
Tidak perlu sinkronisasi memori
Anda perlu menggunakan mekanisme sinkronisasi untuk memastikan Anda benar menangani data

Tidak ada satu resep pun yang mengakomodasi semuanya. Memilih satu sangat tergantung pada konteks dan tugas yang Anda coba capai.

Paralel vs. Konkurensi

Sekarang kita akan melangkah lebih jauh dan menyelami konkurensi. Concurrency sering disalahpahami dan disalahartikan sebagai paralelisme. Bukan itu masalahnya. Concurrency mengimplikasikan penjadwalan kode independen untuk dieksekusi secara kooperatif. Ambil keuntungan dari fakta bahwa sepotong kode menunggu pada operasi I/O, dan selama waktu itu menjalankan bagian kode yang berbeda tetapi independen.

Dengan Python, kita dapat mencapai perilaku konkuren ringan melalui greenlets. Dari perspektif paralelisasi, menggunakan benang atau greenlet setara karena keduanya tidak berjalan paralel. Greenlets bahkan lebih murah untuk dibuat daripada utas. Karena itu, greenlets banyak digunakan untuk melakukan sejumlah besar tugas I/O sederhana, seperti yang biasanya ditemukan di jaringan dan server web.

Sekarang kita mengetahui perbedaan antara benang dan proses, paralel dan bersamaan, kita dapat mengilustrasikan bagaimana tugas yang berbeda dilakukan pada dua paradigma. Inilah yang akan kita lakukan: kita akan berlari, beberapa kali, tugas di luar GIL dan satu di dalamnya. Kami menjalankannya secara serial, menggunakan utas dan menggunakan proses. Mari tentukan tugasnya:

import os
import time
import threading
import multiprocessing

NUM_WORKERS = 4

def only_sleep():
    """ Do nothing, wait for a timer to expire """
    print("PID: %s, Process Name: %s, Thread Name: %s" % (
        os.getpid(),
        multiprocessing.current_process().name,
        threading.current_thread().name)
    )
    time.sleep(1)


def crunch_numbers():
    """ Do some computations """
    print("PID: %s, Process Name: %s, Thread Name: %s" % (
        os.getpid(),
        multiprocessing.current_process().name,
        threading.current_thread().name)
    )
    x = 0
    while x < 10000000:
        x += 1

Kami telah membuat dua tugas. Keduanya lama berjalan, tetapi hanya crunch_numbers yang secara aktif melakukan perhitungan. Mari kita jalankan only_sleep secara serial, multithreaded dan menggunakan banyak proses dan bandingkan hasilnya:

## Run tasks serially
start_time = time.time()
for _ in range(NUM_WORKERS):
    only_sleep()
end_time = time.time()

print("Serial time=", end_time - start_time)

# Run tasks using threads
start_time = time.time()
threads = [threading.Thread(target=only_sleep) for _ in range(NUM_WORKERS)]
[thread.start() for thread in threads]
[thread.join() for thread in threads]
end_time = time.time()

print("Threads time=", end_time - start_time)

# Run tasks using processes
start_time = time.time()
processes = [multiprocessing.Process(target=only_sleep()) for _ in range(NUM_WORKERS)]
[process.start() for process in processes]
[process.join() for process in processes]
end_time = time.time()

print("Parallel time=", end_time - start_time)

Inilah keluaran yang saya miliki (milik Anda harus serupa, meskipun PID dan waktu akan sedikit berbeda):

PID: 95726, Process Name: MainProcess, Thread Name: MainThread
PID: 95726, Process Name: MainProcess, Thread Name: MainThread
PID: 95726, Process Name: MainProcess, Thread Name: MainThread
PID: 95726, Process Name: MainProcess, Thread Name: MainThread
Serial time= 4.018089056015015

PID: 95726, Process Name: MainProcess, Thread Name: Thread-1
PID: 95726, Process Name: MainProcess, Thread Name: Thread-2
PID: 95726, Process Name: MainProcess, Thread Name: Thread-3
PID: 95726, Process Name: MainProcess, Thread Name: Thread-4
Threads time= 1.0047411918640137

PID: 95728, Process Name: Process-1, Thread Name: MainThread
PID: 95729, Process Name: Process-2, Thread Name: MainThread
PID: 95730, Process Name: Process-3, Thread Name: MainThread
PID: 95731, Process Name: Process-4, Thread Name: MainThread
Parallel time= 1.014023780822754

Berikut beberapa pengamatan:

  • Dalam kasus pendekatan serial, hal-hal sudah sangat jelas. Kami menjalankan tugas satu demi satu. Keempat run dijalankan oleh thread yang sama dari proses yang sama.

  • Dengan menggunakan proses, kami memotong waktu eksekusi menjadi seperempat waktu awal, hanya karena tugas dijalankan secara paralel. Perhatikan bagaimana setiap tugas dilakukan dalam proses yang berbeda dan pada MainThread dari proses itu.

  • Menggunakan threads kami mengambil keuntungan dari fakta bahwa tugas dapat dijalankan secara bersamaan. Waktu eksekusi juga dikurangi menjadi seperempat, meskipun tidak ada yang berjalan secara paralel. Di sini adalah bagaimana yang terjadi: kita bertelur thread pertama dan mulai menunggu timer berakhir. Kami menunda pelaksanaannya, membiarkannya menunggu timer berakhir, dan saat ini kami bertelur thread kedua. Kami ulangi ini untuk semua threads. Pada satu saat, pengatur waktu dari utas pertama berakhir sehingga kami mengalihkan pelaksanaannya dan kami menghentikannya. Algoritma ini diulang untuk yang kedua dan untuk semua utas lainnya. Pada akhirnya, hasilnya seolah-olah semuanya berjalan secara paralel. Anda juga akan melihat bahwa empat threads berbeda bercabang dan hidup di dalam proses yang sama: MainProcess.

  • Anda bahkan dapat memperhatikan bahwa pendekatan berulir lebih cepat daripada yang benar-benar paralel. Itu karena overhead proses pemijahan. Seperti yang kami catat sebelumnya, proses pemijahan dan peralihan merupakan operasi yang mahal.

Mari kita lakukan rutinitas yang sama tapi kali ini menjalankan tugas crunch_numbers:

start_time = time.time()
for _ in range(NUM_WORKERS):
    crunch_numbers()
end_time = time.time()

print("Serial time=", end_time - start_time)

start_time = time.time()
threads = [threading.Thread(target=crunch_numbers) for _ in range(NUM_WORKERS)]
[thread.start() for thread in threads]
[thread.join() for thread in threads]
end_time = time.time()

print("Threads time=", end_time - start_time)


start_time = time.time()
processes = [multiprocessing.Process(target=crunch_numbers) for _ in range(NUM_WORKERS)]
[process.start() for process in processes]
[process.join() for process in processes]
end_time = time.time()

print("Parallel time=", end_time - start_time)

Inilah output yang saya dapatkan:

PID: 96285, Process Name: MainProcess, Thread Name: MainThread
PID: 96285, Process Name: MainProcess, Thread Name: MainThread
PID: 96285, Process Name: MainProcess, Thread Name: MainThread
PID: 96285, Process Name: MainProcess, Thread Name: MainThread
Serial time= 2.705625057220459
PID: 96285, Process Name: MainProcess, Thread Name: Thread-1
PID: 96285, Process Name: MainProcess, Thread Name: Thread-2
PID: 96285, Process Name: MainProcess, Thread Name: Thread-3
PID: 96285, Process Name: MainProcess, Thread Name: Thread-4
Threads time= 2.6961309909820557
PID: 96289, Process Name: Process-1, Thread Name: MainThread
PID: 96290, Process Name: Process-2, Thread Name: MainThread
PID: 96291, Process Name: Process-3, Thread Name: MainThread
PID: 96292, Process Name: Process-4, Thread Name: MainThread
Parallel time= 0.8014059066772461

Perbedaan utama di sini adalah hasil dari pendekatan multithread. Kali ini performanya sangat mirip dengan pendekatan serial, dan inilah alasannya: karena ia melakukan perhitungan dan Python tidak melakukan paralelisme yang nyata, thread pada dasarnya berjalan satu demi satu, menghasilkan eksekusi satu sama lain sampai semuanya selesai.

Ekosistem Pemrograman Paralel/Konkurensi Python

Python memiliki API yang kaya untuk melakukan pemrograman paralel/konkurensi. Dalam tutorial ini kami membahas yang paling populer, tetapi Anda harus tahu bahwa untuk setiap kebutuhan yang Anda miliki di domain ini, mungkin ada sesuatu yang sudah ada di luar sana yang dapat membantu Anda mencapai tujuan Anda.

Di bagian selanjutnya, kami akan membangun aplikasi praktis dalam berbagai bentuk, menggunakan semua perpustakaan yang disajikan. Tanpa basa-basi lagi, berikut adalah modul/pustaka yang akan kami bahas:

  • threading: Cara standar bekerja dengan benang dengan Python. Ini adalah pembungkus API tingkat yang lebih tinggi atas fungsi yang diekspos oleh modul _thread, yang merupakan antarmuka tingkat rendah di atas implementasi thread sistem operasi.

  • concurrent.futures: Sebuah bagian modul dari pustaka standar yang menyediakan lapisan abstraksi yang lebih tinggi di atas untaian. Untaian dimodelkan sebagai tugas asynchronous.

  • multiprocessing: Serupa dengan modul threading, menawarkan antarmuka yang sangat mirip tetapi menggunakan proses bukan threads.

  • gevent and greenlets: Greenlets, juga disebut micro-threads, adalah unit eksekusi yang dijadwalkan secara kolaboratif dan dapat melakukan tugas secara bersamaan tanpa banyak overhead.

  • celery: Antrean tugas tingkat tinggi yang didistribusikan. Tugas-tugas tersebut diantrekan dan dijalankan secara bersamaan menggunakan berbagai paradigma seperti multiprocessing atau gevent.

Membangun Aplikasi Praktis

Mengetahui teori itu bagus dan bagus, tetapi cara terbaik untuk belajar adalah membangun sesuatu yang praktis, bukan? Pada bagian ini, kita akan membangun jenis aplikasi klasik melalui semua paradigma yang berbeda.

Mari membangun aplikasi yang memeriksa uptime situs web. Ada banyak solusi di luar sana, yang paling terkenal mungkin adalah Jetpack Monitor dan Uptime Robot. Tujuan dari aplikasi ini adalah untuk memberi tahu Anda ketika situs web Anda tidak aktif sehingga Anda dapat mengambil tindakan dengan cepat. Begini cara kerjanya:

  • Aplikasi berjalan sangat sering di atas daftar URL situs web dan memeriksa apakah situs web tersebut sudah aktif.
  • Setiap situs web harus diperiksa setiap 5-10 menit sehingga waktu henti tidak signifikan.
  • Alih-alih melakukan permintaan HTTP GET klasik, itu melakukan permintaan HEAD sehingga tidak mempengaruhi lalu lintas Anda secara signifikan.
  • Jika status HTTP berada dalam rentang bahaya (400+, 500+), pemilik akan diberi tahu.
  • Pemilik diberitahu melalui email, pesan teks, atau notifikasi push.

Inilah mengapa penting untuk mengambil pendekatan paralel/konkurensi terhadap masalah. Ketika daftar situs web tumbuh, melalui daftar secara serial tidak akan menjamin kita bahwa setiap situs web diperiksa setiap lima menit atau lebih. Situs web bisa turun selama berjam-jam, dan pemiliknya tidak akan diberi tahu.

Mari kita mulai dengan menulis beberapa utilitas:

# utils.py

import time
import logging
import requests


class WebsiteDownException(Exception):
    pass


def ping_website(address, timeout=20):
    """
    Check if a website is down. A website is considered down 
    if either the status_code >= 400 or if the timeout expires
    
    Throw a WebsiteDownException if any of the website down conditions are met
    """
    try:
        response = requests.head(address, timeout=timeout)
        if response.status_code >= 400:
            logging.warning("Website %s returned status_code=%s" % (address, response.status_code))
            raise WebsiteDownException()
    except requests.exceptions.RequestException:
        logging.warning("Timeout expired for website %s" % address)
        raise WebsiteDownException()
        

def notify_owner(address):
    """ 
    Send the owner of the address a notification that their website is down 
    
    For now, we're just going to sleep for 0.5 seconds but this is where 
    you would send an email, push notification or text-message
    """
    logging.info("Notifying the owner of %s website" % address)
    time.sleep(0.5)
    

def check_website(address):
    """
    Utility function: check if a website is down, if so, notify the user
    """
    try:
        ping_website(address)
    except WebsiteDownException:
        notify_owner(address)

Kami sebenarnya membutuhkan daftar situs web untuk mencoba sistem kami. Buat daftar Anda sendiri atau gunakan milik saya:

# websites.py

WEBSITE_LIST = [
    'https://envato.com',
    'http://amazon.co.uk',
    'http://amazon.com',
    'http://facebook.com',
    'http://google.com',
    'http://google.fr',
    'http://google.es',
    'http://google.co.uk',
    'http://internet.org',
    'http://gmail.com',
    'http://stackoverflow.com',
    'http://github.com',
    'http://heroku.com',
    'http://really-cool-available-domain.com',
    'http://djangoproject.com',
    'http://rubyonrails.org',
    'http://basecamp.com',
    'http://trello.com',
    'http://yiiframework.com',
    'http://shopify.com',
    'http://another-really-interesting-domain.co',
    'http://airbnb.com',
    'http://instagram.com',
    'http://snapchat.com',
    'http://youtube.com',
    'http://baidu.com',
    'http://yahoo.com',
    'http://live.com',
    'http://linkedin.com',
    'http://yandex.ru',
    'http://netflix.com',
    'http://wordpress.com',
    'http://bing.com',
]

Biasanya, Anda menyimpan daftar ini dalam database bersama dengan informasi kontak pemilik sehingga Anda dapat menghubungi mereka. Karena ini bukan topik utama dari tutorial ini, dan demi kesederhanaan, kami hanya akan menggunakan daftar Python ini.

Jika Anda membayar perhatian yang sangat baik, Anda mungkin telah memperhatikan dua domain yang sangat panjang dalam daftar yang bukan situs web yang valid (saya harap tidak ada yang membelinya pada saat Anda membaca ini untuk membuktikan bahwa saya salah!). Saya menambahkan dua domain ini untuk memastikan bahwa kami memiliki beberapa situs web di setiap run. Juga, beri nama aplikasi kami UptimeSquirrel.

Pendekatan Serial

Pertama, mari coba pendekatan serial dan lihat seberapa buruk kinerjanya. Kami akan mempertimbangkan ini sebagai baseline.

# serial_squirrel.py

import time


start_time = time.time()

for address in WEBSITE_LIST:
    check_website(address)
        
end_time = time.time()        

print("Time for SerialSquirrel: %ssecs" % (end_time - start_time))

# WARNING:root:Timeout expired for website http://really-cool-available-domain.com
# WARNING:root:Timeout expired for website http://another-really-interesting-domain.co
# WARNING:root:Website http://bing.com returned status_code=405
# Time for SerialSquirrel: 15.881232261657715secs

Pendekatan Threading

Kita akan menjadi sedikit lebih kreatif dengan penerapan pendekatan berulir. Kami menggunakan antrean untuk memasukkan alamat dan membuat threads pekerja agar mereka keluar dari antrean dan memprosesnya. Kami akan menunggu antrean kosong, artinya semua alamat telah diproses oleh pekerja kami.

# threaded_squirrel.py

import time
from queue import Queue
from threading import Thread

NUM_WORKERS = 4
task_queue = Queue()

def worker():
    # Constantly check the queue for addresses
    while True:
        address = task_queue.get()
        check_website(address)
        
        # Mark the processed task as done
        task_queue.task_done()

start_time = time.time()
        
# Create the worker threads
threads = [Thread(target=worker) for _ in range(NUM_WORKERS)]

# Add the websites to the task queue
[task_queue.put(item) for item in WEBSITE_LIST]

# Start all the workers
[thread.start() for thread in threads]

# Wait for all the tasks in the queue to be processed
task_queue.join()

        
end_time = time.time()        

print("Time for ThreadedSquirrel: %ssecs" % (end_time - start_time))

# WARNING:root:Timeout expired for website http://really-cool-available-domain.com
# WARNING:root:Timeout expired for website http://another-really-interesting-domain.co
# WARNING:root:Website http://bing.com returned status_code=405
# Time for ThreadedSquirrel: 3.110753059387207secs

concurrent.futures

Seperti yang dinyatakan sebelumnya, concurrent.futures adalah API tingkat tinggi untuk menggunakan threads. Pendekatan yang kami lakukan di sini menyiratkan penggunaan ThreadPoolExecutor. Kami akan mengirimkan tugas ke pool dan mendapatkan kembali futures, yang merupakan hasil yang akan tersedia bagi kami di masa depan. Tentu saja, kita bisa menunggu semua masa depan menjadi hasil aktual.

# future_squirrel.py

import time
import concurrent.futures

NUM_WORKERS = 4

start_time = time.time()

with concurrent.futures.ThreadPoolExecutor(max_workers=NUM_WORKERS) as executor:
    futures = {executor.submit(check_website, address) for address in WEBSITE_LIST}
    concurrent.futures.wait(futures)

end_time = time.time()        

print("Time for FutureSquirrel: %ssecs" % (end_time - start_time))

# WARNING:root:Timeout expired for website http://really-cool-available-domain.com
# WARNING:root:Timeout expired for website http://another-really-interesting-domain.co
# WARNING:root:Website http://bing.com returned status_code=405
# Time for FutureSquirrel: 1.812899112701416secs

Pendekatan Multiprocessing

Pustaka multiprocessing menyediakan hampir penggantian API untuk pustaka threading. Dalam hal ini, kita akan mengambil pendekatan yang lebih mirip dengan concurrent.futures. Kami sedang menyiapkan multiprocessing.Pool dan mengirimkan tugas ke sana dengan memetakan fungsi ke daftar alamat (pikirkan fungsi map Python klasik).

# multiprocessing_squirrel.py

import time
import socket
import multiprocessing

NUM_WORKERS = 4

start_time = time.time()

with multiprocessing.Pool(processes=NUM_WORKERS) as pool:
    results = pool.map_async(check_website, WEBSITE_LIST)
    results.wait()

end_time = time.time()        

print("Time for MultiProcessingSquirrel: %ssecs" % (end_time - start_time))

# WARNING:root:Timeout expired for website http://really-cool-available-domain.com
# WARNING:root:Timeout expired for website http://another-really-interesting-domain.co
# WARNING:root:Website http://bing.com returned status_code=405
# Time for MultiProcessingSquirrel: 2.8224599361419678secs

Gevent

Gevent adalah alternatif yang populer untuk mencapai konkurensi besar. Ada beberapa hal yang perlu Anda ketahui sebelum menggunakannya:

  • Kode yang dilakukan bersamaan oleh greenlets bersifat deterministik. Berlawanan dengan alternatif lain yang disajikan, paradigma ini menjamin bahwa untuk setiap dua proses yang identik, Anda akan selalu mendapatkan hasil yang sama dalam urutan yang sama.

  • Anda perlu fungsi standar patch monyet sehingga mereka bekerja sama dengan gevent. Inilah yang saya maksud dengan itu. Biasanya, operasi soket memblokir. Kami menunggu operasi selesai. Jika kita berada di lingkungan multithread, scheduler hanya akan beralih ke thread lain sementara yang lain menunggu I/O. Karena kita tidak berada dalam lingkungan multithreaded, gevent menambal fungsi standar sehingga mereka menjadi non-blocking dan mengembalikan kontrol ke penjadwal gevent.

Untuk menginstal gevent, jalankan: pip install gevent

Berikut adalah cara untuk menggunakan gevent untuk melakukan tugas kita menggunakan gevent.pool.Pool:

# green_squirrel.py

import time
from gevent.pool import Pool
from gevent import monkey

# Note that you can spawn many workers with gevent since the cost of creating and switching is very low
NUM_WORKERS = 4

# Monkey-Patch socket module for HTTP requests
monkey.patch_socket()

start_time = time.time()

pool = Pool(NUM_WORKERS)
for address in WEBSITE_LIST:
    pool.spawn(check_website, address)

# Wait for stuff to finish
pool.join()
        
end_time = time.time()        

print("Time for GreenSquirrel: %ssecs" % (end_time - start_time))
# Time for GreenSquirrel: 3.8395519256591797secs

Celery

Celery adalah pendekatan yang sebagian besar berbeda dari apa yang telah kita lihat sejauh ini. Ini adalah pertempuran yang diuji dalam konteks lingkungan yang sangat kompleks dan berkinerja tinggi. Menyiapkan Celery akan membutuhkan sedikit lebih mengutak-atik daripada semua solusi di atas.

Pertama, kita perlu menginstal Celery:

pip install celery

Tugas adalah konsep sentral dalam proyek Celery. Segala sesuatu yang Anda ingin jalankan di dalam Celery perlu menjadi tugas. Celery menawarkan fleksibilitas luar biasa untuk menjalankan tugas: Anda dapat menjalankannya secara sinkron atau asinkron, real-time atau terjadwal, pada mesin yang sama atau pada beberapa mesin, dan menggunakan threads, proses, Eventlet, atau gevent.

Pengaturannya akan sedikit lebih rumit. Celery menggunakan layanan lain untuk mengirim dan menerima pesan. Pesan-pesan ini biasanya tugas atau hasil dari tugas. Kami akan menggunakan Redis dalam tutorial ini untuk tujuan ini. Redis adalah pilihan yang bagus karena sangat mudah untuk menginstal dan mengkonfigurasi, dan itu benar-benar mungkin Anda sudah menggunakannya dalam aplikasi Anda untuk keperluan lain, seperti caching dan pub/sub.

Anda dapat menginstal Redis dengan mengikuti petunjuk pada halaman Redis Quick Start. Jangan lupa untuk menginstal redis Python library, pip install redis, dan bundel yang diperlukan untuk menggunakan Redis dan Celery: pip install celery[redis].

Mulai server Redis seperti ini: $ redis-server

Untuk mulai membuat barang dengan Celery, pertama-tama kita perlu membuat aplikasi Celery. Setelah itu, Celery perlu mengetahui jenis tugas yang mungkin dilakukan. Untuk mencapai itu, kita perlu mendaftarkan tugas ke aplikasi Seledri. Kami akan melakukan ini menggunakan @app.task dekorator:

# celery_squirrel.py

import time
from utils import check_website
from data import WEBSITE_LIST
from celery import Celery
from celery.result import ResultSet

app = Celery('celery_squirrel',
             broker='redis://localhost:6379/0',
             backend='redis://localhost:6379/0')

@app.task
def check_website_task(address):
    return check_website(address)

if __name__ == "__main__":
    start_time = time.time()

    # Using `delay` runs the task async
    rs = ResultSet([check_website_task.delay(address) for address in WEBSITE_LIST])
    
    # Wait for the tasks to finish
    rs.get()

    end_time = time.time()

    print("CelerySquirrel:", end_time - start_time)
    # CelerySquirrel: 2.4979639053344727

Jangan panik jika tidak ada yang terjadi. Ingat, Celery adalah layanan, dan kita harus menjalankannya. Sampai sekarang, kami hanya menempatkan tugas di Redis tetapi tidak memulai Seledri untuk mengeksekusi mereka. Untuk melakukan itu, kita perlu menjalankan perintah ini di folder tempat kode kita berada:

pekerja celery -A do_celery --loglevel=debug --concurrency=4

Sekarang jalankan kembali skrip Python dan lihat apa yang terjadi. Satu hal yang perlu diperhatikan: perhatikan bagaimana kami mengirimkan alamat Redis ke aplikasi Redis kami dua kali. Parameter broker menentukan tempat tugas diteruskan ke Celery, dan backend adalah tempat Celery menempatkan hasilnya sehingga kami dapat menggunakannya di aplikasi kami. Jika kami tidak menentukan backend hasil, tidak ada cara bagi kami untuk mengetahui kapan tugas diproses dan apa hasilnya.

Selain itu, ketahuilah bahwa log sekarang berada dalam output standar proses Celery, jadi pastikan untuk memeriksanya di terminal yang sesuai.

Kesimpulan

Saya harap ini telah menjadi perjalanan yang menarik bagi Anda dan pengenalan yang baik ke dunia pemrograman paralel/konkurensi dengan Python. Ini adalah akhir dari perjalanan, dan ada beberapa kesimpulan yang bisa kami tarik:

  • Ada beberapa paradigma yang membantu kami mencapai komputasi berkinerja tinggi dengan Python.
  • Untuk paradigma multi-threaded, kami memiliki perpustakaan threading dan concurrent.futures.
  • multiprocessing menyediakan antarmuka yang sangat mirip dengan threading tetapi untuk proses daripada threads.
  • Ingat bahwa proses mencapai paralelisme sejati, tetapi mereka lebih mahal untuk dibuat.
  • Ingat bahwa suatu proses dapat memiliki lebih banyak threads yang berjalan di dalamnya.
  • Jangan salahkan paralel untuk bersamaan. Ingat bahwa hanya pendekatan paralel yang mengambil keuntungan dari prosesor multi-core, sedangkan pemrograman konkurensi secara cerdas menjadwalkan tugas sehingga menunggu operasi yang berjalan lama dilakukan sementara secara paralel melakukan komputasi yang sebenarnya.