1. Giới thiệu.

Một thread là một luồng thực thi đơn trong một process. Bởi vì thread có một số đặc tính giống với process, nên đôi khi chúng được gọi là tiến trình nhẹ (lightweight processes). Tuy nhiên, khác với process, các thread không độc lập với nhau. Các thread trong cùng một process chia sẻ với nhau: code section, vùng dữ liệu (data section), các tài nguyên hệ điều hành như file mở và tín hiệu (signals).Nhưng, mỗi thread lại có: Program Counter – PC riêng, tập thanh ghi (register set) riêng, vùng stack riêng.

Multithreading là một kỹ thuật lập trình trong đó một process được chia thành nhiều đơn vị nhỏ hơn gọi là thread, và các thread này có thể chạy đồng thời. Multithreading thường được sử dụng trong các ứng dụng như web server, trò chơi (games), và các hệ thống thời gian thực (real-time systems) để xử lý cùng lúc nhiều tác vụ như: nhận input từ người dùng,

xử lý nền (background processing), các hoạt động I/O khác.


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

Sau đây là một ví dụ:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

pthread_t thread_id1, thread_id2;

typedef struct {
    char message[30];
} thread_data_t;

void *thread_handle(void *args)
{
    pthread_t tid = pthread_self();
    thread_data_t *data = (thread_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 this is %s\n", data->message);
    }
}

int main(int argc, char const *argv[])
{
    /* code */
    int ret;
    thread_data_t data = {0};
    strncpy(data.message, "Multithread demo", sizeof(data.message));

    // Thread 1 with null
    if (ret = pthread_create(&thread_id1, NULL, &thread_handle, NULL)) 
    {
        printf("pthread_create() error number=%d\n", ret);
        return -1;
    }

    // Thread 2 with param
    if (ret = pthread_create(&thread_id2, NULL, &thread_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à thread_handle.

Bên trong hàm thread_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:


3. Chờ một thread kết thúc.

pthread_join() cho phép một thread chờ (wait) cho đến khi một thread khác kết thúc. Nó thường được dùng bởi main thread để đợi các thread con chạy xong.

#include <pthread.h>
#include <stdio.h>pthread_t thread_id1;

void *thread_handle(void* arg) {
    printf("Thread is running.\n");
    return NULL;
}
​
int main() {
    pthread_create(&thread_id1, NULL, &thread_handle, NULL);
​
    // Wait for thread to finish
    pthread_join(thread_id1, NULL);
​
    return 0;
}


Kết quả:


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().


/*
* @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.


Ví dụ: pthread_exit()

#include <pthread.h>
#include <stdio.h>

pthread_t thread_id1;

void *thread_handler(void* arg) {
    printf("Thread id 1 is running.\n");

    // Explicity terminate thread
    pthread_exit(NULL);
}

int main() {
    pthread_create(&thread_id1, NULL, &thread_handler, NULL);

    // Wait for created thread to finish
    pthread_join(thread_id1, NULL);

    return 0;
}


Ví dụ  pthread_cancel():

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

pthread_t thread_id1;

void* thread_handler(void* arg) {
    while(1) {
        printf("Thread is running...\n");
        sleep(1);
    }
    return NULL;
}

int main() {
    pthread_create(&thread_id1, NULL, &thread_handler, NULL);
    sleep(5);
    //Requesting to cancel the thread after 5 seconds.
    pthread_cancel(thread_id1);
    // Wait for the thread to terminate
    pthread_join(thread_id1, NULL);

    printf("Main thread finished.\n");
    return 0;
}


Kết quả:



Reference:

https://www.geeksforgeeks.org/c/multithreading-in-c/

https://viblo.asia/p/thao-tac-voi-thread-bJzKmoYBl9N