线程互斥量
pthread_mutex_t 类型
初始化
一:静态分配,可以设置为常量 PTHREAD_MUTEX_INITIALIZER
二:调用 pthread_mutex_init(3)
若是动态分配,则需要在释放时调用 pthread_mutex_destroy(3)
加锁 / 解锁
pthread_mutex_lock(3) 阻塞至互斥变量解锁
pthread_mutex_trylock(3) 不阻塞,失败返回 EBUSY
pthread_mutex_unlock(3)
将所有线程设计成遵守相同数据访问规则,互斥机制才能工作。
实例:
保护某个数据结构的互斥量
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
/*
* use pthread_mutex_t to protect the struct
* single pthread_mutex_t
*/
struct foo{
int f_count;
pthread_mutex_t f_lock;
int f_id;
/* more */
};
struct foo* foo_alloc(int id){
struct foo *fp;
if ((fp = (struct foo *)malloc(sizeof(foo))) != NULL) {
fp->f_count = 1;
fp->f_id = id;
if (pthread_mutex_init(&fp->f_lock, NULL) != 0) {
printf("pthread_mutex_init fail\n");
free(fp);
return NULL;
}
/* continue initializing */
}
return fp;
}
void foo_hold(struct foo *fp){
if (pthread_mutex_lock(&fp->f_lock) == 0)
fp->f_count++;
pthread_mutex_unlock(&fp->f_lock);
}
void foo_release(struct foo *fp){
pthread_mutex_lock(&fp->f_lock);
if (--fp->f_count == 0) {
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_destroy(&fp->f_lock);
free(fp);
} else {
pthread_mutex_unlock(&fp->f_lock);
}
pthread_mutex_unlock(&fp->f_lock);
}
避免死锁:
注意以下情况:
一:如果线程对同一个互斥量试图加锁两次,它自身就会陷入死锁。
二:相互请求对方所拥有的资源, 导致死锁
可以通过仔细控制互斥量加锁的顺序来避免死锁的发生。
但是,有时候应用程序的结构导致对互斥量的排序是非常困难的。可能需要先释放之前的锁,做好清理工作,之后再继续请求。
实例:
例子一:
两个互斥量的使用方法,同时使用两个时,总是让他们以相同的顺序加锁,这样可以避免死锁。
hashlock 保护散列表 fh 和 f_next
f_next 保护 foo 结构的其他字段
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NHASH 29
#define HASH(n) ((unsigned int)n % NHASH)
/*
* design one
* hashlock protect fh & f_next
* f_lock protect others in struct foo
*/
struct foo *fh[NHASH];
pthread_mutex_t hashlock = PTHREAD_MUTEX_INITIALIZER;
struct foo {
int f_count;
int f_id;
pthread_mutex_t f_lock;
struct foo *f_next;
/* more here */
};
struct foo *foo_alloc(int id){
struct foo *new_foo;
int idx;
if ((new_foo = (struct foo *)malloc(sizeof(struct foo))) != NULL) {
new_foo->f_count = 1;
new_foo->f_id = id;
if (pthread_mutex_init(&new_foo->f_lock, NULL) != 0) {
free(new_foo);
return NULL;
}
idx = HASH(id);
pthread_mutex_lock(&hashlock);
new_foo->f_next = fh[idx];
fh[idx] = new_foo;
pthread_mutex_lock(&new_foo->f_lock);
pthread_mutex_unlock(&hashlock);
/* continue initialize */
pthread_mutex_unlock(&new_foo->f_lock);
} else {
return NULL;
}
return new_foo;
}
void foo_hold(struct foo *fp){
pthread_mutex_lock(&fp->f_lock);
fp->f_count++;
pthread_mutex_unlock(&fp->f_lock);
}
struct foo *foo_find(int id){
struct foo *fp;
//struct foo **temp = &fh[HASH(id)];
pthread_mutex_lock(&hashlock);
for (fp = fh[HASH(id)]; fp != NULL; fp = fp->f_next) { /* *temp = (*temp)++ */
if (fp->f_id == id) {
foo_hold(fp);
break;
}
}
pthread_mutex_unlock(&hashlock);
return fp;
}
void foo_release(struct foo *fp){
pthread_mutex_lock(&fp->f_lock);
if (fp->f_count == 1) {
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_lock(&hashlock);
pthread_mutex_lock(&fp->f_lock);
/* recheck the condition here */
if (fp->f_count != 1) {
fp->f_count--;
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_unlock(&hashlock);
return ;
}
/* remove the list */
int idx = HASH(fp->f_id);
struct foo *tfp = fh[idx];
if (tfp == fp) //in hash table
fh[idx] = fp->f_next;
else {
while (tfp->f_next != fp)
tfp = tfp->f_next;
tfp->f_next = fp->f_next;
}
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_unlock(&hashlock);
free(fp);
} else {
fp->f_count--;
pthread_mutex_unlock(&fp->f_lock);
}
}
例子二:
上面的设计非常复杂,我们重新审视设计方法,改进一下
hashlock 保护 foo 结构的引用计数 f_count
f_lock 保护 foo 结构中其他属性
#include <apue.h>
#include <pthread.h>
/*
* simplify the design
*/
#define NHASH 29
#define HASH(id) ((unsigned int)id % NHASH)
struct foo *fh[NHASH];
pthread_mutex_t hashlock = PTHREAD_MUTEX_INITIALIZER;
struct foo {
int f_count;
pthread_mutex_t f_lock; //protected by hashlock
int f_id;
struct foo *f_next; //protected by hashlock
/* more */
};
struct foo *foo_alloc(int id){
struct foo *new_foo;
if ((new_foo = (struct foo *)malloc(sizeof(struct foo))) != NULL) {
new_foo->f_count = 1;
new_foo->f_id = id;
if (pthread_mutex_init(&new_foo->f_lock, NULL) != 0) {
free(new_foo);
return NULL;
}
int idx = HASH(id);
pthread_mutex_lock(&hashlock);
new_foo->f_next = fh[idx];
fh[idx] = new_foo;
pthread_mutex_lock(&new_foo->f_lock);
pthread_mutex_unlock(&hashlock);
/* initialize other attibute */
pthread_mutex_unlock(&new_foo->f_lock);
}
return new_foo;
}
void foo_hold(struct foo *fp){
pthread_mutex_lock(&hashlock);
fp->f_count++;
pthread_mutex_lock(&hashlock);
}
struct foo *foo_find(int id){
struct foo *fp;
pthread_mutex_lock(&hashlock);
for (fp = fh[HASH(id)]; fp != NULL; fp = fp->f_next) {
if (fp->f_id == id) {
fp->f_count++;
break;
}
}
pthread_mutex_unlock(&hashlock);
return fp;
}
void foo_release(struct foo *fp){
pthread_mutex_lock(&hashlock);
if (fp->f_count == 1) {
int idx = HASH(fp->f_id);
struct foo *tfp = fh[idx];
if (tfp == fp) { // hash table
fh[idx] = fp->f_next;
}
else {
while (tfp->f_next != fp)
tfp = tfp->f_next;
tfp->f_next = fp->f_next;
}
pthread_mutex_unlock(&hashlock);
pthread_mutex_destroy(&fp->f_lock);
free(fp);
} else {
fp->f_count--;
pthread_mutex_unlock(&hashlock);
}
}
可以发现,与例子一相比,这种设计简单多了
围绕散列表 fh 和引用计数 f_count 的锁的排序问题就不存在了。
小结:
多线程问题涉及到这两个问题的折中。
如果锁的粒度太粗,就会出现很多线程阻塞等待相同的锁,这可能不能改善并发性
如果锁的粒度太细,过多的锁的开销会使得系统性能受到影响,而且代码变得复杂
作为程序猿,应该在满足锁需求的情况下,在代码复杂性和性能之间做好平衡!!
感觉很坑,不留神就会写阻塞,忽略各种情况,而且修改写好的,比如增加锁的粒度,基本要重构要么就考虑很周全。
还是多进程大法好!