Cách tạo một luồng Linux trong C
Trên Linux, bạn có thể tạo và quản lý các luồng trong C/C++ bằng thư viện luồng POSIX (pthread). Không giống như các hệ điều hành khác, có rất ít sự khác biệt giữa một luồng và một tiến trình trong Linux. Đó là lý do tại sao Linux thường coi các luồng của nó là các quy trình nhẹ.
Sử dụng thư viện pthread, bạn có thể tạo các luồng, đợi chúng kết thúc và kết thúc chúng một cách rõ ràng.
Mục Lục
Lịch sử sử dụng luồng trên Linux
Trước phiên bản Linux 2.6, việc triển khai luồng chính là LinuxThreads. Việc triển khai này có những hạn chế đáng kể về hiệu suất và hoạt động đồng bộ hóa. Giới hạn về số lượng luồng tối đa có thể chạy đã giới hạn chúng trong 1000 giây.
Năm 2003, một nhóm do các nhà phát triển từ IBM và RedHat đứng đầu đã thành công trong việc tạo ra Thư viện chủ đề POSIX gốc (NPTL) dự án có sẵn. Nó được giới thiệu lần đầu tiên trong RedHat Enterprise phiên bản 3 để giải quyết các vấn đề về hiệu suất với Máy ảo Java trên Linux. Ngày nay, thư viện GNU C chứa các triển khai của cả hai cơ chế luồng.
Cả hai cách này đều không phải là triển khai các luồng màu xanh lá cây mà Máy ảo sẽ quản lý và chạy ở chế độ người dùng thuần túy. Khi bạn sử dụng thư viện pthread, nhân sẽ tạo một luồng mỗi khi chương trình khởi động.
Bạn có thể tìm thông tin cụ thể về luồng cho bất kỳ quy trình đang chạy nào trong các tệp bên dưới /proc/
Logic làm việc của chủ đề
Chủ đề giống như các quy trình hiện đang chạy trên hệ điều hành. Trong các hệ thống đơn xử lý (ví dụ vi điều khiển), nhân hệ điều hành mô phỏng các luồng. Điều này cho phép các giao dịch chạy đồng thời thông qua việc cắt lát.
Một hệ điều hành lõi đơn chỉ có thể thực sự chạy một tiến trình tại một thời điểm. Tuy nhiên, trong các hệ thống đa lõi hoặc đa bộ xử lý, các quy trình này có thể chạy đồng thời.
Tạo luồng trong C
Bạn có thể dùng pthread_create Chức năng tạo thread mới. Các pthread.h tệp tiêu đề bao gồm định nghĩa chữ ký của nó cùng với các chức năng liên quan đến luồng khác. Các luồng sử dụng cùng một không gian địa chỉ và bộ mô tả tệp như chương trình chính.
Thư viện pthread cũng bao gồm hỗ trợ cần thiết cho mutex và các hoạt động có điều kiện cần thiết cho hoạt động đồng bộ hóa.
Khi bạn đang sử dụng các chức năng của thư viện pthread, bạn phải đảm bảo trình biên dịch liên kết các pthread thư viện vào tệp thực thi của bạn. Nếu cần, bạn có thể hướng dẫn trình biên dịch liên kết với thư viện bằng cách sử dụng -l quyền mua:
gcc -o test test_thread.c -lpthread
Hàm pthread_create có chữ ký sau:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
Nó trả về 0 nếu thủ tục thành công. Nếu có vấn đề, nó sẽ trả về mã lỗi khác không. Trong chữ ký chức năng trên:
- Các chủ đề tham số thuộc loại pthread_t. Chuỗi đã tạo sẽ luôn có thể truy cập được với tham chiếu này.
- Các attr tham số cho phép bạn chỉ định hành vi tùy chỉnh. Bạn có thể sử dụng một loạt các chức năng dành riêng cho luồng bắt đầu bằng pthread_attr_ để thiết lập giá trị này. Các tùy chỉnh có thể có là chính sách lập lịch, kích thước ngăn xếp và chính sách tách.
- start_routine chỉ định chức năng mà luồng sẽ chạy.
- tranh luận đại diện cho một cấu trúc dữ liệu chung được luồng chuyển đến hàm.
Đây là một ứng dụng ví dụ:
void *worker(void *data)
{
char *name = (char*)data;
for (int i = 0; i < 120; i++)
{
usleep(50000);
printf("Hi from thread name = %sn", name);
}
printf("Thread %s done!n", name);
return NULL;
}
int main(void)
{
pthread_t th1, th2;
pthread_create(&th1, NULL, worker, "X");
pthread_create(&th2, NULL, worker, "Y");
sleep(5);
printf("Exiting from main programn");
return 0;
}
Các loại chủ đề
Khi một luồng trở về từ chính() trong một ứng dụng, tất cả các luồng kết thúc và hệ thống giải phóng tất cả các tài nguyên mà chương trình đã sử dụng. Tương tự như vậy, khi thoát khỏi bất kỳ luồng nào bằng lệnh như lối ra()chương trình của bạn sẽ chấm dứt tất cả các chủ đề.
với pthread_join thay vào đó, bạn có thể đợi một luồng kết thúc. Chủ đề sử dụng chức năng này sẽ chặn cho đến khi chủ đề dự kiến kết thúc. Các tài nguyên mà chúng sử dụng từ hệ thống không được trả lại ngay cả trong các trường hợp chẳng hạn như chấm dứt các luồng có thể tham gia, không được lên lịch bởi CPU hoặc thậm chí không thể tham gia với ptread_join.
Đôi khi có những tình huống mà việc tham gia với pthread_join không có ý nghĩa; chẳng hạn như nếu không thể dự đoán khi nào chuỗi sẽ kết thúc. Trong trường hợp này, bạn có thể đảm bảo rằng hệ thống sẽ tự động trả về tất cả các tài nguyên tại điểm mà luồng trả về.
Để đạt được điều này, bạn nên bắt đầu các chủ đề có liên quan với TÁCH trạng thái. Khi bắt đầu một chủ đề, TÁCH trạng thái có thể được đặt thông qua các giá trị thuộc tính luồng hoặc với pthread_detach hàm số:
int pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
int pthread_detach(pthread_t thread);
Đây là một ví dụ sử dụng pthread_join(). Thay thế chức năng chính trong chương trình đầu tiên bằng cách sau:
int main(void)
{
pthread_t th1, th2;
pthread_create(&th1, NULL, worker, "X");
pthread_create(&th2, NULL, worker, "Y");
sleep(5);
printf("exiting from main programn");
pthread_join(th1, NULL);
pthread_join(th2, NULL);
return 0;
}
Khi bạn biên dịch và chạy chương trình, đầu ra của bạn sẽ là:
Hi from thread Y
Hi from thread X
Hi from thread Y
...
Hi from thread Y
exiting from main program
Hi from thread X
...
Hi from thread X
Thread X done!
Hi from thread Y
Thread Y done!
Chấm dứt chủ đề
Bạn có thể hủy một chuỗi bằng lệnh gọi pthread_cancel, chuyển chuỗi tương ứng pthread_t Tôi:
int pthread_cancel(pthread_t thread);
Bạn có thể thấy điều này đang hoạt động trong đoạn mã sau. Một lần nữa, chỉ có chính chức năng là khác nhau:
int main(void)
{
pthread_t th1, th2;
pthread_create(&th1, NULL, worker, "X");
pthread_create(&th2, NULL, worker, "Y");
sleep(1);
printf("====> Cancelling Thread Y!!n");
pthread_cancel(th2);
usleep(100000);
printf("====> Cancelling Thread X!n");
pthread_cancel(th1);
printf("exiting from main programn");
return 0;
}
Tại sao chủ đề được tạo ra?
Các hệ điều hành luôn cố gắng chạy các luồng trên một hoặc nhiều CPU, từ danh sách tự tạo hoặc từ danh sách luồng do người dùng tạo. Một số luồng không thể chạy vì chúng đang chờ tín hiệu đầu vào/đầu ra từ phần cứng. Họ cũng có thể đang chờ đợi một cách tự nguyện, chờ phản hồi từ một chuỗi khác hoặc có một chuỗi khác chặn họ.
Bạn có thể điều chỉnh các tài nguyên mà bạn phân bổ cho các chuỗi mà bạn tạo bằng pthread. Đây có thể là một chính sách lập lịch trình tùy chỉnh hoặc bạn có thể chọn các thuật toán lập lịch trình như FIFO hoặc Round-robin nếu muốn.