1. 问题引入:互斥锁问题,假设现在有两个资源A和B,一个线程先拿A再拿B,另一个则相反,这样导致的问题就是死锁,即两个线程无休止的互相等待
#include
#include
#include
#include
pthread_mutex_t g_mtxa = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t g_mtxb = PTHREAD_MUTEX_INITIALIZER;
void* thread_proc1 (void* arg) {
pthread_t tid = pthread_self ();
printf ("%lu线程:等待A...\n", tid);
pthread_mutex_lock (&g_mtxa);
printf ("%lu线程:获得A!\n", tid);
sleep (1);
printf ("%lu线程:等待B...\n", tid);
pthread_mutex_lock (&g_mtxb);
printf ("%lu线程:获得B!\n", tid);
pthread_mutex_unlock (&g_mtxb);
printf ("%lu线程:释放B。\n", tid);
pthread_mutex_unlock (&g_mtxa);
printf ("%lu线程:释放A。\n", tid);
return NULL;
}
void* thread_proc2 (void* arg) {
pthread_t tid = pthread_self ();
printf ("%lu线程:等待B...\n", tid);
pthread_mutex_lock (&g_mtxb);
printf ("%lu线程:获得B!\n", tid);
sleep (1);
printf ("%lu线程:等待A...\n", tid);
pthread_mutex_lock (&g_mtxa);
printf ("%lu线程:获得A!\n", tid);
pthread_mutex_unlock (&g_mtxa);
printf ("%lu线程:释放A。\n", tid);
pthread_mutex_unlock (&g_mtxb);
printf ("%lu线程:释放B。\n", tid);
return NULL;
}
int main (void) {
pthread_t tid1;
int error = pthread_create (&tid1, NULL, thread_proc1, NULL);
if (error) {
fprintf (stderr, "pthread_create: %s\n", strerror (error));
return -1;
}
pthread_t tid2;
if ((error = pthread_create (&tid2, NULL, thread_proc2,
NULL)) != 0) {
fprintf (stderr, "pthread_create: %s\n", strerror (error));
return -1;
}
if ((error = pthread_join (tid1, NULL)) != 0) {
fprintf (stderr, "pthread_join: %s\n", strerror (error));
return -1;
}
if ((error = pthread_join (tid2, NULL)) != 0) {
fprintf (stderr, "pthread_join: %s\n", strerror (error));
return -1;
}
printf ("大功告成!\n");
return 0;
}
思考:解决上面问题的核心就是让一个资源同时使用A和B,使用完在释放,这样就不会出现互相等待导致死锁的问题。
2. 条件变量:条件变量是一种同步机制,允许线程挂起,直到共享数据上的某些条件得到满足。条件变量上的基本操作有:触发条件(当条件变为 true 时);等待条件,挂起线程直到其他线程触发条件。条件变量必须和互斥锁一起使用
int pthread_cond_init (pthread_cond_t* cond,const pthread_condattr_t* attr);// 初始化条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;// 作用和上个函数等价
int pthread_cond_wait (pthread_cond_t* cond,pthread_mutex_t* mutex);// 使调用线程睡入条件变量cond,同时释放互斥锁mutex,
int pthread_cond_timedwait (pthread_cond_t* cond, pthread_mutex_t* mutex, const struct timespec* abstime);// abstime 指定的时间内 cond 未触发,互斥量 mutex 被重新加锁
struct timespec {
time_t tv_sec; // Seconds
long tv_nsec; // Nanoseconds [0 - 999999999]
};
int pthread_cond_signal (pthread_cond_t* cond);// 从条件变量cond中唤出一个线程, 令其重新获得原先的互斥锁
注意:被唤出的线程此刻将从pthread_cond_wait函数中返回,但如果该线程无法获得原先的锁,则会继续阻塞在加锁上。
int pthread_cond_broadcast (pthread_cond_t* cond);// 从条件变量cond中唤出所有线程
int pthread_cond_destroy (pthread_cond_t* cond);//销毁条件变量释放资源
#include
#include
#include
#include
#include
#define MAX_STOCK 20 // 仓库容量
char g_storage[MAX_STOCK]; // 仓库
size_t g_stock = 0; // 当前库存
pthread_mutex_t g_mtx = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t g_full = PTHREAD_COND_INITIALIZER; // 满仓
pthread_cond_t g_empty = PTHREAD_COND_INITIALIZER; // 空仓
// 显示库存
void show (const char* who, const char* op, char prod) {
printf ("%s:", who);
size_t i;
for (i = 0; i < g_stock; i++)
printf ("%c", g_storage[i]);
printf ("%s%c\n", op, prod);
}
// 生产者线程
void* producer (void* arg) {
const char* who = (const char*)arg;
for (;;) {
pthread_mutex_lock (&g_mtx);
// if (g_stock >= MAX_STOCK) { //此时应该对条件进行再判断
while (g_stock >= MAX_STOCK) {
printf ("%s:满仓!\n", who);
pthread_cond_wait (&g_full, &g_mtx);
}
char prod = 'A' + rand () % 26;
show (who, "<-", prod);
g_storage[g_stock++] = prod;
pthread_cond_broadcast (&g_empty);
pthread_mutex_unlock (&g_mtx);
usleep ((rand () % 100) * 1000);
}
return NULL;
}
// 消费者线程
void* customer (void* arg) {
const char* who = (const char*)arg;
for (;;) {
pthread_mutex_lock (&g_mtx);
// if (! g_stock) {
while (! g_stock) {
printf ("%s:空仓!\n", who);
pthread_cond_wait (&g_empty, &g_mtx);
}
char prod = g_storage[--g_stock];
show (who, "->", prod);
pthread_cond_broadcast (&g_full);
pthread_mutex_unlock (&g_mtx);
usleep ((rand () % 100) * 1000);
}
return NULL;
}
int main (void) {
srand (time (NULL));
pthread_attr_t attr;
int error = pthread_attr_init (&attr);
if (error) {
fprintf (stderr, "pthread_attr_init: %s\n", strerror (error));
return -1;
}
if ((error = pthread_attr_setdetachstate (&attr,//修改线程分离状态属性
PTHREAD_CREATE_DETACHED)) != 0) {
fprintf (stderr, "pthread_attr_setdetachstate: %s\n",
strerror (error));
return -1;
}
pthread_t tid;
if ((error = pthread_create (&tid, &attr, producer,
"生产者1")) != 0) {
fprintf (stderr, "pthread_create: %s\n", strerror (error));
return -1;
}
if ((error = pthread_create (&tid, &attr, producer,
"生产者2")) != 0) {
fprintf (stderr, "pthread_create: %s\n", strerror (error));
return -1;
}
if ((error = pthread_create (&tid, &attr, customer,
"消费者1")) != 0) {
fprintf (stderr, "pthread_create: %s\n", strerror (error));
return -1;
}
if ((error = pthread_create (&tid, &attr, customer,
"消费者2")) != 0) {
fprintf (stderr, "pthread_create: %s\n", strerror (error));
return -1;
}
getchar ();
return 0;
}
注意:
1.处于分离状态的线程终止后自动释放线程资源,且不能被pthread_join函数等待
2. 进行条件再判断的原因:两个生产者,两个消费者,signal一次只能叫醒一个,所以现在使用broadst广播出去假设现在生产者生产了一个产品,他就广播一下,这时候俩消费者都醒了,其中一个拿到锁然后消耗了资源,再解锁,这时候另一个直接拿到锁,然而此时已经没产品,溢出导致段错误(如下图)。这就是条件被别人消耗了的导致的情况。
4.哲学家就餐问题:
五个哲学家围坐在一张圆桌周围,每个哲学家面前都有一盘通心粉。由于通心粉很滑,所以需要两把叉子才能夹住。相邻两个盘子之间放有一把叉子。哲学家的生活中有两种交替活动时段:即吃饭和思考。当一个哲学家觉得饿了时,他就试图分两次去取其左边和右边的叉子,每次拿一把,但不分次序。如果成功地得到了两把叉子,就开始吃饭,吃完后放下叉子继续思考。那么我们要做的就是为每一个哲学家写一段描述其行为的程序,且决不会死锁吗?
解决方案:让哲学家不看筷子看人,如果两边的人都没吃,就自己吃,吃完后就通知两边的人。
#include
#include
#include
#include
#include
#define DINERS 5 // 就餐人数
// 就餐者状态
typedef enum tag_State {
THINKING, // 正在思考
HUNGRY, // 正在挨饿
EATING // 正在吃饭
} ESTAT;
pthread_mutex_t g_mtx = PTHREAD_MUTEX_INITIALIZER;
// 就餐者条件变量数组
pthread_cond_t g_conds[DINERS] = {
PTHREAD_COND_INITIALIZER, PTHREAD_COND_INITIALIZER,
PTHREAD_COND_INITIALIZER, PTHREAD_COND_INITIALIZER,
PTHREAD_COND_INITIALIZER};
// 就餐者姓名数组
const char* g_names[DINERS] = {
"哲学家1", "哲学家2", "哲学家3", "哲学家4", "哲学家5"};
// 就餐者状态数组
ESTAT g_stats[DINERS] = {
THINKING, THINKING, THINKING, THINKING, THINKING};
// 吃饭
void eat (int i) {
int l = (i + 1) % DINERS; // 左邻
int r = (i + DINERS - 1) % DINERS; // 右邻
pthread_mutex_lock (&g_mtx);
while (g_stats[l] == EATING || g_stats[r] == EATING) { //只有左右两个人都不吃的时候自己才能吃
g_stats[i] = HUNGRY;
printf ("%s:快点吧,饿死我了~~~\n", g_names[i]);
pthread_cond_wait (&g_conds[i], &g_mtx); //等待的时候把锁放开
}
g_stats[i] = EATING;
printf ("%s:终于可以吃一口了!\n", g_names[i]);
pthread_mutex_unlock (&g_mtx);
usleep ((rand () % 100) * 10000); //吃的时间随机给出
}
// 思考
void think (int i) {
int l = (i + 1) % DINERS; // 左邻
int r = (i + DINERS - 1) % DINERS; // 右舍
pthread_mutex_lock (&g_mtx);
g_stats[i] = THINKING;
printf ("%s:吃饱了,开始思考...\n", g_names[i]);
pthread_cond_signal (&g_conds[l]); // 令其重新获得原先的互斥锁
pthread_cond_signal (&g_conds[r]);
pthread_mutex_unlock (&g_mtx);
usleep ((rand () % 100) * 10000);
}
// 就餐者线程
void* diner (void* arg) {
int i = (int)arg; //拿到哲学家的索引号
for (;;) {
eat (i);
think (i);
}
return NULL;
}
int main (void) {
srand (time (NULL));
pthread_attr_t attr;
int error = pthread_attr_init (&attr);
if (error) {
fprintf (stderr, "pthread_attr_init: %s\n", strerror (error));
return -1;
}
if ((error = pthread_attr_setdetachstate (&attr,
PTHREAD_CREATE_DETACHED)) != 0) {
fprintf (stderr, "pthread_attr_setdetachstate: %s\n",
strerror (error));
return -1;
}
size_t i;
pthread_t tid;
for (i = 0; i < DINERS; ++i)
if ((error = pthread_create (&tid, &attr, diner,
(void*)i)) != 0) {
fprintf (stderr, "pthread_create: %s\n", strerror (error));
return -1;
}
getchar ();
return 0;
}