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

Condition variable và producer-consumer

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

  • Dùng condition variable để chờ một điều kiện
  • Giải thích vì sao phải dùng while không phải if
  • Cài producer-consumer với hàng đợi vòng
  • Phân biệt signal và broadcast

Mutex bảo vệ dữ liệu, nhưng không giúp một luồng chờ tới khi dữ liệu sẵn sàng. Condition variable làm điều đó: một luồng ngủ tới khi luồng khác báo một điều kiện đã đúng. Đây là nền tảng của mẫu producer-consumer.

#Vấn đề chờ điều kiện

/* Mot luong can CHO toi khi co du lieu. Cach ngay tho: */
while (dem == 0) { }            /* CHO BAN: quay vong ban CPU 100% */

/* Hoac them mutex: */
for (;;) {
    pthread_mutex_lock(&m);
    if (dem > 0) break;
    pthread_mutex_unlock(&m);   /* van quay vong, van ban CPU */
}

/* Ca hai deu BAN CPU: luong cho lien tuc kiem tra dieu kien,
   dot CPU ma khong lam gi. Voi nhieu luong cho, CPU chay het
   cong suat vao viec kiem tra vo ich.

   Con dung: luong cho phai NGU, va chi thuc day khi dieu kien
   CO THE da doi. Do la viec cua condition variable. */

#Condition variable

Condition variable
Một cơ chế để một luồng ngủ tới khi một điều kiện đúng, và một luồng khác đánh thức nó khi thay đổi điều kiện. Luôn đi kèm một mutex bảo vệ điều kiện. cond_wait tự mở khoá khi ngủ và khoá lại khi tỉnh.
Mẫu chờ và báo
static pthread_mutex_t m  = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t  cv = PTHREAD_COND_INITIALIZER;
static int san_sang = 0;

/* --- Luong CHO --- */
pthread_mutex_lock(&m);
while (!san_sang)                       /* PHAI la while, xem muc sau */
    pthread_cond_wait(&cv, &m);         /* ngu, tu MO khoa m; tinh -> khoa lai */
xu_ly();
pthread_mutex_unlock(&m);

/* --- Luong BAO --- */
pthread_mutex_lock(&m);
san_sang = 1;
pthread_cond_signal(&cv);               /* danh thuc MOT luong dang cho */
pthread_mutex_unlock(&m);

#Vì sao while không phải if

#Producer-consumer

pc.c, hàng đợi vòng với hai condition variable
#define N 4
static int buf[N], dem = 0, dau = 0, cuoi = 0;
static pthread_mutex_t m       = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t  co_cho  = PTHREAD_COND_INITIALIZER;   /* buffer khong day */
static pthread_cond_t  co_hang = PTHREAD_COND_INITIALIZER;   /* buffer khong rong */

void san_xuat(int v) {
    pthread_mutex_lock(&m);
    while (dem == N)                        /* day -> cho co cho */
        pthread_cond_wait(&co_cho, &m);
    buf[cuoi] = v; cuoi = (cuoi + 1) % N; ++dem;
    pthread_cond_signal(&co_hang);          /* bao co hang moi */
    pthread_mutex_unlock(&m);
}

int tieu_thu(void) {
    pthread_mutex_lock(&m);
    while (dem == 0)                        /* rong -> cho co hang */
        pthread_cond_wait(&co_hang, &m);
    int v = buf[dau]; dau = (dau + 1) % N; --dem;
    pthread_cond_signal(&co_cho);           /* bao co cho trong */
    pthread_mutex_unlock(&m);
    return v;
}
terminal
# Producer gui 1..20, consumer cong lai, tong = 210
gcc -O2 -pthread pc.c -o pc.exe && ./pc.exe
da tieu thu 20 muc, tong = 210 (ky vong 210)
if và một condvar
static pthread_cond_t cv; /* mot condvar cho ca hai */ void san_xuat(int v) { pthread_mutex_lock(&m); if (dem == N) pthread_cond_wait(&cv, &m); /* if: thuc day gia hong */ ... pthread_cond_signal(&cv); /* co the danh thuc nham producer khac */ }
while và hai condvar
static pthread_cond_t co_cho, co_hang; /* hai condvar rieng */ void san_xuat(int v) { pthread_mutex_lock(&m); while (dem == N) /* while: an toan voi thuc day gia */ pthread_cond_wait(&co_cho, &m); ... pthread_cond_signal(&co_hang); /* danh thuc dung consumer */ }

#signal và broadcast

HàmĐánh thứcDùng khi
pthread_cond_signalMột luồng đang chờMột muc dữ liệu, chỉ một luồng xử lý được
pthread_cond_broadcastTất cả luồng đang chờTrạng thái đổi ảnh hưởng nhiều luồng

Tự làm thử

  1. Viết một luồng chờ và một luồng báo bằng condition variable.
  2. Đổi while thành if quanh cond_wait và tìm trường hợp nó sai.
  3. Cài producer-consumer với hàng đợi vòng và hai condition variable.
  4. Mở rộng thành nhiều producer và nhiều consumer.
  5. Dùng broadcastđể báo "dừng" cho mọi luồng đang chờ.
  6. So signal và broadcast về số lần đánh thức thừa với ba luồng chờ.

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

  • Condition variable cho một luồng ngủ tới khi điều kiện đúng, thay vì quay vòng bận CPU.
  • cond_wait mở khoá, ngủ, khoá lại một cách nguyên tử, nên không bỏ lỡ tín hiệu.
  • Luôn dùng while quanh cond_wait vì thức dậy giả và nhiều luồng cùng chờ.
  • Producer-consumer dùng hai condition variable cho hai điều kiện đầy và rỗng.
  • signal đánh thức một luồng, broadcast tất cả; khi phân vân dùng broadcast.