Traffine I/O

Bahasa Indonesia

2023-03-31

Kesetaraan Waktu dalam Python

Pengenalan Kesetaraan Waktu dalam Python

Kesetaraan waktu dalam Python adalah kemampuan untuk menjalankan beberapa tugas secara simultan sehingga resource yang tersedia dapat dimanfaatkan dengan maksimal. Hal ini penting untuk meningkatkan kinerja aplikasi yang terikat pada CPU dan I/O. Python menyediakan beberapa modul untuk mencapai kesetaraan waktu, termasuk modul threading, multiprocessing, dan concurrent.futures.

Modul threading

Modul threading memungkinkan Anda untuk membuat dan mengelola thread di Python. Thread adalah ringan dan berbagi ruang memori yang sama dengan proses induk, sehingga cocok untuk tugas I/O-bound.

Pembuatan Thread

Untuk membuat thread, Anda dapat menggunakan kelas Thread dari modul threading. Berikut adalah contoh membuat dan memulai dua thread:

python
import threading

def print_numbers():
    for i in range(1, 6):
        print(f"Number: {i}")

def print_letters():
    for letter in 'abcde':
        print(f"Letter: {letter}")

# Membuat thread
thread1 = threading.Thread(target=print_numbers)
thread2 = threading.Thread(target=print_letters)

# Memulai thread
thread1.start()
thread2.start()

# Menggabungkan thread untuk menunggu penyelesaiannya
thread1.join()
thread2.join()

print("Semua thread selesai.")

Sinkronisasi Thread

Untuk menghindari kondisi perlombaan (race condition), Anda dapat menggunakan mekanisme sinkronisasi thread seperti penguncian (lock). Contoh berikut menunjukkan penggunaan penguncian untuk melindungi resource bersama:

python
import threading

counter = 0
counter_lock = threading.Lock()

def increment_counter():
    global counter
    with counter_lock:
        counter += 1

threads = [threading.Thread(target=increment_counter) for _ in range(100)]

for thread in threads:
    thread.start()

for thread in threads:
    thread.join()

print(f"Counter value: {counter}")

Thread Daemon

Thread daemon adalah thread latar belakang yang secara otomatis berakhir ketika program utama keluar. Untuk membuat thread daemon, atur atribut daemon menjadi True:

python
daemon_thread = threading.Thread(target=some_function, daemon=True)
daemon_thread.start()

Komunikasi Thread

Thread dapat berkomunikasi menggunakan struktur data bersama atau dengan menggunakan objek sinkronisasi tingkat lebih tinggi seperti Queue:

python
import threading
import queue

def worker(q):
    while True:
        item = q.get()
        if item is None:
            break
        print(f"Processing: {item}")
        q.task_done()

work_queue = queue.Queue()

for i in range(10):
    work_queue.put(i)

worker_thread = threading.Thread(target=worker, args=(work_queue,))
worker_thread.start()

work_queue.join()
work_queue.put(None)
worker_thread.join()

Modul multiprocessing

Modul multiprocessing menyediakan dukungan untuk paralelisme menggunakan proses. Hal ini cocok untuk tugas CPU-bound, karena proses dapat berjalan pada ruang memori terpisah dan memanfaatkan penuh beberapa inti CPU.

Pembuatan Proses

Untuk membuat proses, gunakan kelas Process dari modul multiprocessing:

python
import multiprocessing

def square(x):
    print(f"{x} squared is {x*x}")

processes = [multiprocessing.Process(target=square, args=(i,)) for i in range(5)]

for process in processes:
    process.start()

for process in processes:
    process.join()

print("All processes are done.")

Sinkronisasi Proses

Anda dapat menggunakan primitif sinkronisasi seperti Lock dan Semaphore dari modul multiprocessing untuk melindungi resource bersama:

python
import multiprocessing

counter = multiprocessing.Value('i', 0)
counter_lock = multiprocessing.Lock()

def increment_counter(counter, counter_lock):
    with counter_lock:
        counter.value += 1

processes = [multiprocessing.Process(target=increment_counter, args=(counter, counter_lock)) for _ in range(100)]

for process in processes:
    process.start()

for process in processes:
    process.join()

print(f"Counter value: {counter.value}")

Komunikasi Antar-Proses

Anda dapat menggunakan Pipe atau Queue dari modul multiprocessing untuk komunikasi antar-proses:

python
import multiprocessing

def square(x, output_queue):
    output_queue.put(x * x)

input_data = [1, 2, 3, 4, 5]
output_queue = multiprocessing.Queue()

processes = [multiprocessing.Process(target=square, args=(i, output_queue)) for i in input_data]

for process in processes:
    process.start()

for process in processes:
    process.join()

while not output_queue.empty():
    print(f"Squared value: {output_queue.get()}")

Modul Concurrent.futures

Modul concurrent.futures menyediakan antarmuka tingkat tinggi untuk menjalankan callable secara asinkron menggunakan thread atau proses.

ThreadPoolExecutor

ThreadPoolExecutor adalah antarmuka tingkat tinggi untuk mengirimkan tugas ke thread pool:

python
import concurrent.futures

def square(x):
    return x * x

with concurrent.futures.ThreadPoolExecutor() as executor:
    results = executor.map(square, range(1, 6))

for result in results:
    print(f"Squared value: {result}")

ProcessPoolExecutor

ProcessPoolExecutor adalah antarmuka tingkat tinggi untuk mengirimkan tugas ke pool proses:

python
import concurrent.futures

def square(x):
    return x * x

with concurrent.futures.ProcessPoolExecutor() as executor:
    results = executor.map(square, range(1, 6))

for result in results:
    print(f"Squared value: {result}")

Objek Future

Objek Future menggabungkan hasil dari perhitungan yang mungkin belum selesai. Mereka dapat digunakan dengan executor untuk memantau kemajuan tugas:

python
import concurrent.futures

def square(x):
    return x * x

with concurrent.futures.ThreadPoolExecutor() as executor:
    futures = [executor.submit(square, i) for i in range(1, 6)]

for future in concurrent.futures.as_completed(futures):
    print(f"Squared value: {future.result()}")

Kasus Penggunaan: S3

Amazon S3 (Simple Storage Service) adalah layanan penyimpanan cloud yang banyak digunakan. Untuk mengunduh file secara bersamaan dari S3, Anda dapat menggunakan pustaka boto3 bersama dengan modul concurrent.futures.

python
import boto3
import concurrent.futures

s3 = boto3.client("s3")

def download_file(bucket, key, local_filename):
    print(f"Downloading {key} from {bucket}")
    s3.download_file(bucket, key, local_filename)
    print(f"Downloaded {key} from {bucket} to {local_filename}")

bucket_name = "your-bucket-name"
keys = ["file1.txt", "file2.txt", "file3.txt"]
local_filenames = ["downloaded_file1.txt", "downloaded_file2.txt", "downloaded_file3.txt"]

with concurrent.futures.ThreadPoolExecutor() as executor:
    futures = [executor.submit(download_file, bucket_name, key, local_filename) for key, local_filename in zip(keys, local_filenames)]

for future in concurrent.futures.as_completed(futures):
    print(f"Completed download: {future.result()}")

Untuk mengunggah file secara bersamaan ke bucket S3, Anda dapat menggunakan pustaka yang sama seperti sebelumnya: boto3 dan concurrent.futures. Berikut adalah contoh cara mengunggah file secara bersamaan ke bucket S3:

python
import boto3
import concurrent.futures

s3 = boto3.client("s3")

def upload_file(bucket, key, local_filename):
    print(f"Uploading {local_filename} to {bucket}/{key}")
    with open(local_filename, "rb") as file:
        s3.upload_fileobj(file, bucket, key)
    print(f"Uploaded {local_filename} to {bucket}/{key}")

bucket_name = "your-bucket-name"
keys = ["uploaded_file1.txt", "uploaded_file2.txt", "uploaded_file3.txt"]
local_filenames = ["file1.txt", "file2.txt", "file3.txt"]

with concurrent.futures.ThreadPoolExecutor() as executor:
    futures = [executor.submit(upload_file, bucket_name, key, local_filename) for key, local_filename in zip(keys, local_filenames)]

for future in concurrent.futures.as_completed(futures):
    print(f"Completed upload: {future.result()}")

Dalam kedua contoh, kita menggunakan ThreadPoolExecutor dari modul concurrent.futures, karena tugasnya mengalami pembatasan I/O. Pustaka boto3 menangani operasi S3 yang sebenarnya, sementara modul concurrent.futures mengelola konkurensinya.

Pertimbangan Kinerja dan Perbandingan

Meskipun thread sangat ringan dan cocok untuk tugas I/O-bound, mereka menderita pembatasan Global Interpreter Lock (GIL) di CPython, yang mencegah beberapa thread menjalankan bytecode Python secara bersamaan. Oleh karena itu, mereka mungkin tidak memberikan kinerja optimal untuk tugas CPU-bound.

Multiproses mengatasi pembatasan GIL dengan menggunakan proses terpisah dengan ruang memori mereka sendiri, sehingga cocok untuk tugas CPU-bound. Namun, ini memperkenalkan overhead tambahan karena komunikasi antar-proses dan serialisasi data.

concurrent.futures menyediakan antarmuka tingkat tinggi untuk mengelola thread dan proses, yang mengabstraksi beberapa kompleksitas saat bekerja dengan modul threading dan multiprocessing.

Ketika memilih model konkurensi, pertimbangkan sifat tugas Anda (I/O-bound atau CPU-bound) dan kompromi antara overhead thread/proses dan potensi peningkatan kinerja.

Referensi

https://docs.python.org/3/library/threading.html
https://realpython.com/intro-to-python-threading/
https://www.pythontutorial.net/python-concurrency/python-threading/

Ryusei Kakujo

researchgatelinkedingithub

Focusing on data science for mobility

Bench Press 100kg!