锁可以实现互斥,条件变量可以实现同步。
为了保护共享数据的正确性,我们需要把锁和条件变量联合起来使用,这就是 管程(Monitor) 的作用。
管程将需要保护的一组共享数据封装起来,利用锁与条件变量实现对这些数据的保护,保证同时只有一个函数能够使用这组数据。
对于一个栈(Stack)数据结构来说,如果在多线程的情况下实现 push()
和 pop()
功能,程序就会变得不安全 —— 在一个线程正在调用 push()
时,如果内核切换到另一个线程,另一个线程运行 pop()
,那么就可能出现多种不同的结果;更糟糕的是,如果栈里只剩下一个元素,此时有两个线程调用 pop()
,我们可能会在两个线程中获得同一个元素。为了避免这种不安全因素,我们利用管程来保护栈的内部状态。
示例程序:
#include <stdlib.h>
#include <pthread.h>
struct stack_entry {
void *data;
struct stack_entry *next;
};
struct stack {
struct stack_entry *head;
int capacity;
int count;
pthread_mutex_t mutex;
pthread_cond_t is_full;
pthread_cond_t is_empty;
};
struct stack *new_stack(int capacity) {
struct stack *my_stack = calloc(1, sizeof(struct stack));
if (my_stack == NULL) {
return NULL;
}
my_stack->head = NULL;
my_stack->capacity = capacity;
my_stack->count = 0;
pthread_mutex_init(&my_stack->mutex, NULL);
pthread_cond_init(&my_stack->is_full, NULL);
pthread_cond_init(&my_stack->is_empty, NULL);
return my_stack;
}
int push(struct stack *my_stack, void *data) {
if (pthread_mutex_lock(&my_stack->mutex) != 0) {
return -1;
}
while (my_stack->count == my_stack->capacity) {
pthread_cond_wait(&my_stack->is_full, &my_stack->mutex);
}
struct stack_entry *new_entry = calloc(1, sizeof(struct stack_entry));
if (new_entry == NULL) {
return -1;
}
new_entry->data = data;
new_entry->next = my_stack->head;
my_stack->head = new_entry;
my_stack->count += 1;
pthread_cond_signal(&my_stack->is_empty);
if (pthread_mutex_unlock(&my_stack->mutex) != 0) {
return -1;
}
return 0;
}
void *pop(struct stack *my_stack) {
if (pthread_mutex_lock(&my_stack->mutex) != 0) {
return (void *)-1;
}
while (my_stack->count == 0) {
pthread_cond_wait(&my_stack->is_empty, &my_stack->mutex);
}
struct stack_entry *top = my_stack->head;
my_stack->head = top->next;
void *data = top->data;
free(top);
my_stack->count -= 1;
pthread_cond_signal(&my_stack->is_full);
if (pthread_mutex_unlock(&my_stack->mutex) != 0) {
return (void *)-1;
}
return data;
}
int main() {
return 0;
}