Trong lập trình hệ thống Linux, đa luồng là một phương pháp quan trọng để thực hiện lập trình song song. Thư viện POSIX threads (pthread) cung cấp một bộ các giao diện thao tác với luồng, nắm vững cách sử dụng các giao diện này và cấu hình thuộc tính của luồng là nền tảng để viết các chương trình đa luồng hiệu quả và ổn định. Bài viết sẽ giải thích chi tiết về các hàm cơ bản của pthread và phân tích kỹ thuộc tính phân tách của luồng cũng như ứng dụng của nó.
Các Hàm Cơ Bản của pthread
1. pthread_create: Tạo Luồng
pthread_create là hàm tạo luồng, sau khi gọi thành công sẽ tạo ra một luồng mới và thực thi hàm nhiệm vụ được chỉ định.
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
- thread: con trỏ đến biến kiểu
pthread_t, dùng để lưu ID của luồng mới. - attr: cấu hình thuộc tính của luồng, truyền
NULLnếu sử dụng thuộc tính mặc định. - start_routine: hàm mục tiêu mà luồng sẽ thực thi, có kiểu
void *(*)(void *). - arg: đối số được truyền vào hàm
start_routine.
Giá trị trả về:
- Trả về 0 nếu thành công.
- Trả về mã lỗi khác 0 nếu thất bại.
2. pthread_exit: Kết thúc Luồng
pthread_exit được sử dụng để kết thúc một luồng một cách chủ động, có thể chỉ định giá trị trả về của luồng.
void pthread_exit(void *retval);
- retval: giá trị trả về của luồng, kiểu
void*.
Lưu ý:
- Nếu luồng chính gọi
pthread_exit, chỉ có luồng chính kết thúc còn các luồng con vẫn tiếp tục. - Khi hàm của luồng kết thúc bằng cách return thì tương đương với gọi
pthread_exit.
3. pthread_join: Chờ và Thu Hồi Luồng
pthread_join là hàm chờ đợi một luồng cụ thể kết thúc và thu hồi tài nguyên của nó, đồng thời có thể lấy trạng thái thoát của luồng.
int pthread_join(pthread_t thread, void **retval);
- thread: ID của luồng cần chờ đợi.
- retval: con trỏ đến
void*, dùng để lưu giá trị trả về của luồng.
Giá trị trả về:
- Trả về 0 nếu thành công.
- Trả về mã lỗi khác 0 nếu thất bại.
Mục đích chính:
- Thu hồi tài nguyên của luồng: tránh tình trạng "luồng zombie" (luồng kết thúc nhưng không giải phóng tài nguyên).
- Đồng bộ hóa luồng: luồng chính có thể chờ luồng con hoàn thành trước khi tiếp tục.
4. pthread_attr_init: Khởi Tạo Thuộc Tính Luồng
pthread_attr_init được sử dụng để khởi tạo một đối tượng thuộc tính luồng, thiết lập nó về các giá trị mặc định.
int pthread_attr_init(pthread_attr_t *attr);
- attr: con trỏ đến đối tượng thuộc tính luồng kiểu
pthread_attr_t.
Giá trị trả về:
- Trả về 0 nếu thành công.
- Trả về mã lỗi khác 0 nếu thất bại.
5. pthread_attr_destroy: Giải Phóng Thuộc Tính Luồng
pthread_attr_destroy được sử dụng để giải phóng đối tượng thuộc tính luồng đã được khởi tạo.
int pthread_attr_destroy(pthread_attr_t *attr);
- attr: đối tượng thuộc tính luồng đã khởi tạo.
Giá trị trả về:
- Trả về 0 nếu thành công.
- Trả về mã lỗi khác 0 nếu thất bại.
6. pthread_attr_setdetachstate: Thiết Lập Thuộc Tính Phân Tách
pthread_attr_setdetachstate là một trong những hàm thiết lập thuộc tính quan trọng, dùng để thiết lập thuộc tính "được nối / phân tách" của luồng.
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
- attr: đối tượng thuộc tính luồng đã khởi tạo.
- detachstate: trạng thái phân tách của luồng, có thể là:
PTHREAD_CREATE_DETACHED: thuộc tính phân tách.PTHREAD_CREATE_JOINABLE: thuộc tính được nối (giá trị mặc định).
Giá trị trả về:
- Trả về 0 nếu thành công.
- Trả về mã lỗi khác 0 nếu thất bại.
Phân Tích Thuộc Tính Phân Tách Của Luồng
Thuộc tính phân tách (detachstate) của luồng xác định cách hệ thống thu hồi tài nguyên sau khi luồng kết thúc, là một thuộc tính quan trọng trong lập trình đa luồng.
So Sánh Hai Thuộc Tính
| Loại Thuộc Tính | Tính Chất Chính | Ứng Dụng |
|---|---|---|
| Thuộc tính Được Nối (JOINABLE) | Sau khi kết thúc, cần gọi pthread_join để thu hồi tài nguyên; có thể lấy trạng thái thoát; đồng bộ hóa |
Trường hợp cần đợi kết quả của luồng |
| Thuộc tính Phân Tách (DETACHED) | Sau khi kết thúc, hệ thống tự động thu hồi tài nguyên; không cần gọi pthread_join; không thể lấy trạng thái thoát |
Các tác vụ nền không cần đợi kết quả |
Lưu Ý Quan Trọng
- Nếu luồng có thuộc tính JOINABLE nhưng không gọi
pthread_join, sau khi kết thúc sẽ trở thành "luồng zombie", chiếm tài nguyên hệ thống. - Gọi
pthread_jointrên luồng có thuộc tính DETACHED sẽ trả về lỗi (EINVAL). - Có thể sử dụng hàm
pthread_detachđể thiết lập thuộc tính phân tách sau khi tạo luồng (không cần cấu hình đối tượng thuộc tính trước đó).
Ví Dụ Thực Hiện: Tạo và Thu Hồi Các Loạt Thuộc Tính Luồng
Ví Dụ 1: Luồng Mặc Định (JOINABLE)
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *thuc_hanh(void *tham_so) {
int so = *(int *)tham_so;
printf("Luồng %d đang chạy\n", so);
pthread_exit((void *)(so + 100));
}
int main() {
pthread_t id;
int tham_so = 1;
void *ket_qua;
int ret = pthread_create(&id, NULL, thuc_hanh, &tham_so);
if (ret != 0) {
perror("pthread_create thất bại");
exit(1);
}
ret = pthread_join(id, &ket_qua);
if (ret != 0) {
perror("pthread_join thất bại");
exit(1);
}
printf("Luồng kết thúc, kết quả: %ld\n", (long)ket_qua);
return 0;
}
Ví Dụ 2: Luồng Phân Tách (DETACHED)
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *thuc_hanh_detached(void *tham_so) {
int so = *(int *)tham_so;
printf("Luồng phân tách %d đang chạy\n", so);
sleep(2);
printf("Luồng phân tách %d kết thúc\n", so);
return NULL;
}
int main() {
pthread_t id;
pthread_attr_t thuoc_tinh;
int tham_so = 2;
int ret = pthread_attr_init(&thuoc_tinh);
if (ret != 0) {
perror("pthread_attr_init thất bại");
exit(1);
}
ret = pthread_attr_setdetachstate(&thuoc_tinh, PTHREAD_CREATE_DETACHED);
if (ret != 0) {
perror("pthread_attr_setdetachstate thất bại");
exit(1);
}
ret = pthread_create(&id, &thuoc_tinh, thuc_hanh_detached, &tham_so);
if (ret != 0) {
perror("pthread_create thất bại");
exit(1);
}
pthread_attr_destroy(&thuoc_tinh);
sleep(3);
printf("Luồng chính kết thúc\n");
return 0;
}
Biên Dịch và Chạy Chương Trình
Khi biên dịch cần liên kết thư viện pthread (-lpthread):
gcc thread_vd.c -o thread_vd -lpthread
./thread_vd