Mặc dù Python cung cấp module threading để tạo các luồng, nhưng các luồng được tạo theo cách này bị giới hạn bởi Global Interpreter Lock (GIL), chỉ cho phép một luồng thực thi mã Python tại một thời điểm trên một lõi CPU. Điều này ngăn cản việc tận dụng tối đa tài nguyên đa lõi. Để đạt được sự đồng thời thực sự, nơi mỗi luồng có thể chạy trên các lõi CPU khác nhau, chúng ta có thể sử dụng một phương pháp thay thế bằng cách gọi các hàm thư viện C.
Cách tiếp cận này liên quan đến việc viết một thư viện C sử dụng hàm pthread_create tiêu chuẩn của POSIX để tạo các luồng độc lập. Các luồng này, không bị ảnh hưởng bởi GIL, có thể thực thi song song trên các lõi CPU khác nhau.
Thư viện C
Tạo một tệp C (ví dụ: concurrent_threads.c) với nội dung sau:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
// Hàm sẽ được thực thi bởi các luồng mới
void* workerThread(void* arg) {
// Vòng lặp vô hạn để giữ cho luồng bận rộn, mô phỏng một tác vụ
while (1) {
// Không làm gì cả, chỉ để giữ CPU bận
__asm__("nop");
}
return NULL;
}
// Hàm để tạo một luồng mới
void createNewThread() {
int error_code;
pthread_t thread_id;
// Tạo luồng mới, truyền hàm workerThread làm điểm bắt đầu
error_code = pthread_create(&thread_id, NULL, workerThread, NULL);
if (error_code != 0) {
fprintf(stderr, "Lỗi khi tạo luồng: %d\n", error_code);
return;
}
// Luồng đã được tạo thành công
}
Biên dịch mã C này thành một thư viện chia sẻ động:
$ gcc -fPIC -shared -o libconcurrent.so concurrent_threads.c
Gọi từ Python để Đạt được Đa Luồng Thực sự
Bây giờ, bạn có thể sử dụng module ctypes của Python để tải thư viện C đã biên dịch và gọi hàm createNewThread. Việc gọi hàm này nhiều lần sẽ tạo ra các luồng độc lập, mỗi luồng có khả năng chạy trên một lõi CPU khác nhau.
from ctypes import cdll, RTLD_GLOBAL
import time
import os
# Tải thư viện chia sẻ động
# Đảm bảo libconcurrent.so nằm trong cùng thư mục hoặc trong đường dẫn hệ thống
try:
lib = cdll.LoadLibrary("./libconcurrent.so")
except OSError as e:
print(f"Không thể tải thư viện libconcurrent.so: {e}")
exit(1)
# Lấy tham chiếu đến hàm createNewThread từ thư viện C
create_thread_func = lib.createNewThread
# Không cần định nghĩa argtypes và restype vì hàm không nhận tham số và trả về void
if __name__ == "__main__":
print(f"Chương trình chính đang chạy với PID: {os.getpid()}")
# Tạo hai luồng độc lập bằng cách gọi hàm C hai lần
print("Đang tạo luồng đầu tiên...")
create_thread_func()
print("Đang tạo luồng thứ hai...")
create_thread_func()
print("Hai luồng đã được tạo. Luồng chính sẽ tiếp tục chạy.")
# Luồng chính tiếp tục chạy
while True:
print("Luồng chính: Đang hoạt động!")
time.sleep(2)
Khi bạn chạy tập lệnh Python này, bạn có thể quan sát việc sử dụng CPU bằng các công cụ như top hoặc htop. Bạn sẽ thấy rằng hai luồng được tạo ra từ thư viện C đang tiêu thụ tài nguyên CPU một cách độc lập, với mỗi luồng có thể chiếm gần 100% một lõi CPU. Điều này xác nhận rằng chúng đang chạy song song thực sự trên các lõi khác nhau, vượt qua những hạn chế của GIL.
# Ví dụ đầu ra của lệnh top (có thể thay đổi tùy thuộc vào hệ thống của bạn)
top - 14:05:15 up 1 day, 1:30, 2 users, load average: 0.45, 0.30, 0.25
Tasks: 180 total, 2 running, 178 sleeping, 0 stopped, 0 zombie
%Cpu0 : 99.5 us, 0.5 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu2 : 99.8 us, 0.2 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu3 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 8005700 total, 2500100 used, 5505600 free, 400500 buffers
KiB Swap: 0 total, 0 used, 0 free. 750800 cached Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1234 user 20 0 50000 8000 5000 R 199.0 0.1 0:05.67 python
5678 user 20 0 30000 4000 3000 R 0.3 0.0 0:00.01 top
...
Trong đầu ra top, bạn có thể thấy tiến trình Python (python) có mức sử dụng CPU tổng hợp cao, cho thấy hai luồng đang hoạt động tích cực trên các lõi CPU khác nhau (ví dụ: %Cpu0 và %Cpu2 có mức sử dụng cao).