Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 45.126 phút đọc

pthread, create và join

Sau bài này bạn sẽ làm được

  • Phân biệt luồng với tiến trình trên sáu tiêu chí
  • Tạo và chờ luồng bằng pthread_create và pthread_join
  • Tránh bẫy truyền địa chỉ biến vòng lặp
  • Liên kết với cờ -pthread

Luồng là nhiều dòng thực thi trong cùng một tiến trình, chia sẻ bộ nhớ. Đó là sức mạnh và cũng là nguy hiểm của chúng. Chương này chạy được trên bản MinGW qua winpthreads, nên các bản ghi là kết quả thật.

#Luồng và tiến trình

Tiến trìnhLuồng
Không gian địa chỉRiêng biệtChia sẻ
Tạo mớiChậm (fork)Nhanh
Giao tiếpIPC: pipe, socket, bộ nhớ chia sẻBiến chung, trực tiếp
Một cái sậpKhông ảnh hưởng cái khácSập cả tiến trình
Chuyển ngữ cảnhChậmNhanh
Đồng bộ hoáÍt cầnBắt buộc
Luồng (thread)
Một dòng thực thi độc lập bên trong một tiến trình. Nhiều luồng của cùng tiến trình chia sẻ vùng mã, dữ liệu toàn cục, heap, và bảng mô tả tệp; nhưng mỗi luồng có ngăn xếp, thanh ghi, và errno riêng.

#create và join

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

void *cong_viec(void *arg) {
    long id = (long)arg;
    printf("Luong %ld dang chay\n", id);
    return (void *)(id * 10);          /* gia tri tra ve */
}

int main(void) {
    pthread_t t[4];

    for (long i = 0; i < 4; ++i)
        if (pthread_create(&t[i], NULL, cong_viec, (void *)i) != 0) {
            perror("pthread_create");
            return 1;
        }

    for (int i = 0; i < 4; ++i) {
        void *kq;
        pthread_join(t[i], &kq);       /* cho luong xong, lay ket qua */
        printf("Luong %d tra ve %ld\n", i, (long)kq);
    }
    return 0;
}
terminal
gcc -O2 -pthread tcj.c -o tcj.exe && ./tcj.exe
Luong 0 dang chay
Luong 1 dang chay
Luong 3 dang chay
Luong 2 dang chay
Luong 0 tra ve 0
Luong 1 tra ve 10
Luong 2 tra ve 20
Luong 3 tra ve 30

#Giá trị trả về

Trả về và nhận qua void *
void *cong_viec(void *arg) {
    long id = (long)arg;
    return (void *)(id * 10);          /* dong goi ket qua vao void* */
}

void *kq;
pthread_join(t[i], &kq);               /* nhan qua con tro toi void* */
long gia_tri = (long)kq;               /* mo goi */

/* Ham luong nhan MOT void* va tra MOT void*. De truyen so nguyen,
   ep qua (void*) va nguoc lai. De truyen cau truc, truyen con tro
   toi no.

   CAN THAN: KHONG tra ve con tro toi bien CUC BO cua ham luong: */
void *sai(void *a) {
    (void)a;
    int ket_qua = 42;
    return &ket_qua;                   /* SAI: ket_qua bien mat khi ham xong */
}
/* Ngan xep cua luong bi giai phong khi ham tra ve, nen con tro
   tro toi vung da bi thu hoi. Dung con tro toi bien tinh, heap,
   hoac ep gia tri truc tiep qua void*. */

#Bẫy truyền tham số

arg.c, bẫy truyền địa chỉ biến vòng lặp
#include <pthread.h>
#include <stdio.h>

void *in(void *a) { printf("%d ", *(int *)a); return NULL; }

int main(void) {
    pthread_t t[4];

    printf("SAI (truyen &i):   ");
    for (int i = 0; i < 4; ++i)
        pthread_create(&t[i], NULL, in, &i);      /* SAI: moi luong tro toi CUNG i */
    for (int i = 0; i < 4; ++i) pthread_join(t[i], NULL);

    printf("\nDUNG (ban sao):    ");
    int args[4];
    for (int i = 0; i < 4; ++i) {
        args[i] = i;                              /* moi luong mot ban sao rieng */
        pthread_create(&t[i], NULL, in, &args[i]);
    }
    for (int i = 0; i < 4; ++i) pthread_join(t[i], NULL);
    printf("\n");
    return 0;
}
terminal
gcc -O0 -pthread arg.c -o arg.exe && ./arg.exe
SAI (truyen &i):   3 4 4 4
DUNG (ban sao):    0 1 2 3

Tự làm thử

  1. Tạo bốn luồng, mỗi luồng in số của nó, và chờ tất cả bằng pthread_join.
  2. Cho mỗi luồng trả về một giá trị và in giá trị đó ở luồng chính.
  3. Quên -pthread khi liên kết và đọc lỗi.
  4. Tái hiện bẫy &i, chạy nhiều lần, và quan sát kết quả khác nhau.
  5. Trả về con trỏ tới biến cục bộ của hàm luồng và quan sát hậu quả với công cụ phát hiện lỗi bộ nhớ.
  6. Tạo một luồng detach và giải thích khi nào dùng nó thay vì join.

Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.

Tóm tắt

  • Luồng chia sẻ bộ nhớ của tiến trình; đổi lại một luồng sập giết cả tiến trình và đồng bộ là bắt buộc.
  • pthread_create tạo, pthread_join chờ và lấy kết quả; liên kết với -pthread.
  • Truyền và nhận qua void *; không trả con trỏ tới biến cục bộ của hàm luồng.
  • Mỗi luồng phải được join hoặc detach, nếu không rò rỉ tài nguyên.
  • Không truyền &i của biến vòng lặp; cho mỗi luồng một bản sao riêng.