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:
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:
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
:
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
:
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
:
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:
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:
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:
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:
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:
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
.
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:
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