- vừa được xem lúc

Thao tác với Thread

0 0 49

Người đăng: Phong Sv

Theo Viblo Asia

1. Giới thiệu

Trong bài này, chúng ta sẽ tập trung vào cách thread được tạo ra, hủy bỏ và định danh của thread trong hệ thống (thread ID). Xuyên suốt bài học sẽ là các ví dụ kèm theo để giải thích cách lập trình cơ bản với luồng.

2. Thread ID

Cũng giống như một tiến trình được xác định bởi một process ID, một thread trong process được xác định bởi một thread ID. Ở đây, có một số điểm thú vị giữa process ID và thread ID cần được làm rõ.

  • process ID là duy nhất trên toàn hệ thống, trong đó thread ID là duy nhất trong một tiến trình (process).
  • process ID là một giá trị số nguyên nhưng thread ID không nhất thiết phải là một giá trị số nguyên. Nó có thể là một structure.
  • process ID có thể được in ra rất dễ dàng trong khi thread ID thì không.

Thread ID sẽ được đại diện bởi kiểu pthread_t. Phần lớn các trường hợp thread ID sẽ là một structure nên để so sánh hai thread ID với nhau ta cần một function có thể thực hiện công việc này (Đối với process ID là một số nguyên thì việc so sánh đơn giản hơn)

#include <pthread.h> /*
* @return Trả về 0 nếu tid1 khác tid2, khác không nếu tid1 = tid2.
*/ int pthread_equal(pthread_t tid1, pthread_t tid2); 

Ngoài ra, một thread có thể thu được ID của chính nó thông qua việc gọi hàm pthread_self().

include <pthread.h> /*
* @return Trả về thread ID của thread đang gọi pthread_self().
*/ pthread_t pthread_self(void);

Lấy một ví dụ về việc sử dụng hai chức năng trên, giả sử rằng ta có một danh sách liên kết chứa dữ liệu của các threads khác nhau. Mỗi một node trong danh sách liên kết này chứa thread ID và dữ liệu tương ứng của thread ID đó. Lúc này, bất cứ khi nào thread muốn lấy dữ liệu của nó từ danh sách liên kết. Bước đầu tiên nó cần phải lấy được thread ID của chính mình bằng việc gọi pthread_self() và sau đó nó sẽ gọi pthread_equal() để kiếm tra node nào đang chứa dữ liệu mà nó cần.

3. Tạo thread mới

Thông thường, khi chương trình (program) được khởi chạy và trở thành một tiến trình (process), lúc này bản thân tiến trình đó chính là một single-thread (tiến trình đơn luồng), tiến trình tạo nhiều hơn 1 threads được gọi là mutiple-thread (tiến trình đa luồng) . Vì vậy, ta có thể kết luận rằng mọi tiến trình đều có ít nhất một thread. Trong đó, thread chứa hàm main được gọi là main thread.

Để tạo một thread mới chúng ta sử dụng hàm pthread_create() với prototype như sau:

/*
* @return Trả về 0 nếu thành công, trả về một số dương nếu là một lỗi.
*/ int pthread_create(pthread_t *threadID, const pthread_attr_t *attr, void *(*start)(void *), void *arg);

Hàm pthread_create() bao gồm 4 tham số, chúng ta sẽ cùng tìm hiểu về chúng.

  • Đối số đầu tiên : Một khi tiến trình được gọi thành công, đối số đầu tiên sẽ giữ thread ID của thread mới được tạo.
  • Đối số thứ hai : Thông thường giá trị này đặt thành NULL.
  • Đối số thứ ba : Là một con trỏ hàm (function pointer) . Mỗi một thread sẽ chạy riêng một function, địa chỉ của function này sẽ được truyền tại đối số thứ ba để linux biết được thread này bắt đầu chạy từ đâu.
  • Đối số thứ tư : Đối sô arg được truyền vào có kiểu void, điều này cho phép ta truyền bất kì kiểu dữ liệu nào vào hàm xử lý của thread. Hoặc giá trị này có thể là NULL nếu ta không muốn truyền bất cứ đối số nào. Điều này sẽ được thể hiện rõ ràng hơn trong ví dụ dưới đây.

3.1. Ví dụ 1

Ví dụ sau đây sẽ sử dụng 3 hàm được mô tả bên trên.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h> pthread_t thread_id1, thread_id2; typedef struct { char name[30]; char msg[30];
} thr_data_t; static void *thr_handle(void *args) { pthread_t tid = pthread_self(); thr_data_t *data = (thr_data_t *)args; if (pthread_equal(tid, thread_id1)) { printf("I'm thread_id1\n\n"); } else { printf("I'm thread_id2\n"); printf("Hello %s, welcome to join %s\n", data->name, data->msg); }
} int main(int argc, char const *argv[])
{ /* code */ int ret; thr_data_t data = {0}; strncpy(data.name, "phonglt9", sizeof(data.name)); strncpy(data.msg, "Posix thread programming\n", sizeof(data.msg)); if (ret = pthread_create(&thread_id1, NULL, &thr_handle, NULL)) { printf("pthread_create() error number=%d\n", ret); return -1; } if (ret = pthread_create(&thread_id2, NULL, &thr_handle, &data)) { printf("pthread_create() error number=%d\n", ret); return -1; } sleep(5); return 0;
}

Đoạn mã trên sử dụng pthread_create() để tạo ra hai thread mới. Đối với thread thứ hai, chúng ta sẽ truyền thêm dữ liệu cho nó qua đối số arg, cả hai threads đều sử dụng chung một hàm xử lý là thr_handle.

Bên trong hàm thr_handle chúng ta sử dụng pthread_self() và pthread_equal() để xác định xem đâu là thread đầu tiên và thread thứ hai được tạo ra. Nếu là thread thứ hai thì in ra dữ liệu được truyền từ bên ngoài vào.

Kết quả sau khi compile và cho chạy chương trình như sau:

4. Kết thúc thread

Một thead đang thực thi có thể bị kết thúc bởi một trong số những cách sau:

  • Hàm xử lý của thread thực hiện return.
  • Hàm xử lý của thread thực hiện gọi pthread_exit().
  • Thread bị hủy bỏ bởi hàm pthread_cancel().
  • Bất kì threads nào gọi exit() hoặc main thread thực hiện return. Nếu điều này xảy ra thì tất cả các threads còn lại sẽ bị kết thúc ngay lập tức.

Hàm pthread_exit() kết thúc thread đang gọi và chỉ định giá trị trả về, giá trị này có thể nhận được trong một thread khác bằng cách gọi pthread_join(). Prototype của pthread_exit().

include <pthread.h> /*
* @param[out] reval Giá trị trả về khi kết thúc thread.
*/ void pthread_exit(void *retval);

Ta thấy rằng hàm này chỉ chấp nhận một đối số, đó là giá trị trả về từ thread đang gọi hàm này. Giá trị trả về này được truy cập bởi thread cha đang đợi thread này kết thúc và có thể được truy cập bởi một thread khác thông qua pthread_join() vừa giải thích ở trên.

Điểm thú vị ở đây là cơ chế quản lý của thread khá tương đồng với process. Chúng ta sẽ cùng thảo luận điểm này ở bài tiếp theo, tránh làm bài đọc bị quá tải.

5. Kết luận

Kết thúc bài viết, người đọc cần nắm điểm sau:

  • Phân biệt được program, process và thread.
  • Phân biệt được thread ID với process ID và cách sử dụng.
  • Cách tạo lập và kết thúc thread.

Bình luận

Bài viết tương tự

- vừa được xem lúc

Docker: Chưa biết gì đến biết dùng (Phần 3: Docker-compose)

1. Mở đầu. . .

0 0 121

- vừa được xem lúc

Các command trên ubuntu (chiếm 80%) - phần 5

Hello 500 ae, sau 4 số trong seri này mình thấy có vẻ ae có hứng thú đọc chủ đề này ghê. Hi vọng những gì mình tìm hiểu được sẽ giúp ích được cho nhiều bạn hơn.

0 0 51

- vừa được xem lúc

Những lệnh command shell linux cơ bản có thể bạn chưa biết - Phần 1

Bài viết này sẽ liệt kê một số command cơ bản thường được dùng trong linux, tiện dụng cho các bạn khi sử dụng linux mà có thể bạn chưa biết. Tôi là ai, câu lệnh này sẽ cho bạn biết bạn đang sử dụng linux bằng tài khoản người dùng nào.

0 0 55

- vừa được xem lúc

Cách sử dụng lệnh SCP để truyền tệp an toàn

SCP (secure copy) là một tiện ích dòng lệnh cho phép bạn sao chép an toàn các tệp và thư mục giữa hai vị trí. . From your local system to a remote system. Between two remote systems from your local system.

0 0 55

- vừa được xem lúc

Người ta làm cách nào để backup thường xuyên thư mục rất lớn?

. Vấn đề về sao lưu thư mục lớn. Mình có lưu "sương sương" 300GB các tệp tin của người dùng upload lên, như hình ảnh hay các tệp đính kèm.

0 0 49

- vừa được xem lúc

Các command trên ubuntu (chiếm 80%) - phần 4

Sau một kì nghỉ tết trong thời buổi đại dịch vừa qua. Không còn những buổi dong chơi đi chúc tết nữa. Ở nhà ra số tiếp theo cho anh em đây. Dưới đây sẽ là 2 command được sử dụng nhiều nhất khi sử dụng file.

0 0 52