`

(第四章 3)锁与嵌套锁的实现

 
阅读更多

一、锁

      在书中,我们实现一个锁的背景是:对已经写好的单线程的双向链表做一个改进,使得它可以支持单线程和多线程两种方式。并且满足以下要求:

      要求1. 对于多线程版本,由实现者(链表)加锁/解锁,以防调用者忘记解锁造成死锁。

      解决思路:这个好办,只要在实现链表的时候在相应的操作函数中加锁即可。

 

      要求2. 区分单线程和多线程版本时,即不需要链接不同的库,也不需要用宏来进行控制,完全可以在运行时切换。

      解决思路:

                       单线程版本调用DList* dlist=dlist_create(NULL, NULL, NULL);

                       多线程版本调用DList* dlist=dlist_create(NULL, NULL, locker_pthread_create());

 

      要求3. 保持双向链表的通用性,不依赖特定的平台。

      解决思路:锁的实现恰恰是依赖平台的(这是“变化的因素”),因此需要“隔离变化”。要隔离变化,自然要用到“回调函数”。这里的回调函数无非加锁/解锁/销毁锁,对于一组相关的回调函数可以把他们整合到一个“接口”中。在下面的代码中我们将看到,C语言的接口中是回调函数(Java的接口中是抽象方法)

 

      综上,思路应该是: 隔离变化 --> 回调函数 --> 接口(其实就是一组相关的回调函数)

 

二、嵌套锁(和装饰者模式)

      上面实现的锁其实完全在多线程环境(单线程呢?废话,单线程还用什么“锁”)下使用,真正使用的应该是这里我们将实现的嵌套锁。因为使用锁的对象是“线程”,而不是“函数”。

      设想后一种情况:比如在dlist_insert()中调用了dlist_length(),进入dlist_insert()时已加了一次锁,再调用dlist_length()时又加了一次锁,这时就死锁了。如果调用dlist_length()前先解锁,用完在加锁,就不能保证dlist_insert()的原子性了,因为在这一解一加的过程中,可能其他线程趁虚而入了。

      所以,真实情况下使用锁,我们都应该使用这里实现的嵌套锁,因为他对线程进行了加锁

      使用嵌套锁的目的是:指定“使用锁的对象”是线程,这样同一线程的不同函数可以同时使用“锁住的对象”

 

调用方式(体会装饰者模式)

Locker* locker=locker_pthread_create();
Locker* nest_locker=locker_nest_create(locker, (TaskSelfFunc)pthread_self);
DList* dlist=dlist_create(NULL, NULL, nest_locker);

 

三、代码导读

这两节是书上的4.2和4.3两节,代码集中在 "$系统程序员成长计划/4/3/locker_nest"中,下面对其中的每个文件做说明:

 

Makefile: 编译并运行嵌套锁测试

typedef.h: 定义 枚举Ret、宏DECLS_BEGIN、宏DECLS_END、宏return_if_fail(p)、宏return_val_if_fail(p)、SAFE_FREE(p)

 

dlist.hdlist.c: 双向链表

 

locker.h: 锁接口(普通锁和嵌套锁均要实现这个接口)

typedef Ret  (*LockerLockFunc)(Locker* thiz);
typedef Ret  (*LockerUnlockFunc)(Locker* thiz);
typedef void (*LockerDestroyFunc)(Locker* thiz);

struct _Locker
{
	LockerLockFunc    lock;
	LockerUnlockFunc  unlock;
	LockerDestroyFunc destroy;

	char priv[0];
};

 

 

locker_pthread.hlocker_pthread.c: 实现锁接口Locker的普通锁。使用了libpthread.so共享库(POSIX Thread函数库, is a POSIX standard for threads

 

      这里简要的复习一下libpthread库中锁的使用方法——

首先,在编译链接的时候一定要这样做,才能引入pthread共享函数库:

       gcc -shared -lpthread dlist.c locker_pthread.c locker_nest.c -o libdlist.so (做成共享库)

   或gcc -DDLIST_TEST -lpthread dlist.c locker_pthread.c locker_nest.c -o dlist_test(测试)

 

#include <pthread.h>

pthread_mutex_t  mutex;

1. pthread_mutex_init(&mutex, NULL);

2. pthread_mutex_lock(&mutex);

3. pthread_mutex_unlock(&mutex);

4. pthread_mutex_destroy(&mutex);

 

locker_nest.hlocker_nest.c: 实现锁接口Locker的嵌套锁,指定使用锁的对象是”线程“。即对于上锁的对象,不允许两个不同的线程同时访问。

int pthread_self(void)可以返回当前线程的id

 

注意:

       1. 区分锁机制中”锁住的对象“和”使用锁的对象“两个概念。

 

首先来看看锁的使用方法:

Ret dlist_insert(DList* thiz, size_t index, void* data){
	return_val_if_fail(thiz != NULL, RET_INVALID_PARAMS); 

	dlist_lock(thiz);
        ...
	dlist_unlock(thiz);

	return ret;
}

 

       如果使用locker_pthread实现的普通锁,“锁住的对象”是thiz双向链表对象,但并没有指定“使用锁的对象”,也就是说在加解锁之间不能调用本线程的其他函数。

       如果使用locker_nest实现的嵌套锁,“锁住的对象”是thiz双向链表对象,指定“使用锁的对象”是线程,也就是说在加解锁之间可以调用本线程的其他函数(这样,insert()内部就可以调用length()了)。

 

       2. “”和“锁住的对象”是两个不同的实体。
 1). “锁”是实现Locker的对象
    i.e. 具体可以由locker_pthread_create()或locker_nest_create(...)创建
 2). “锁住的对象”则是像DList的实例这样的东西,他的创建需要传入一个锁的实例,以便在使用前后加解锁
    i.e. 具体可以这样创建:DList* dlist=dlist_create(NULL, NULL, nest_locker);

分享到:
评论

相关推荐

    多核计算与程序设计(2009年3月1日出版)第四五部分

    第4部分介绍多核共享资源计算方面的内容,也是本书中最重要的内容,讲解了分布式计算设计模式如线程分组竞争模式、条件同步模式、批量私有化处理模式、数据本地化模式等。这部分中讲解了本书中几个最重要的程序:...

    数据库系统实现

    第4章 索引结构 4.1 顺序文件上的索引 4.1.1 顺序文件 4.1.2 稠密索引 4.1.3 稀疏索引 4.1.4 多级索引 4.1.5 重复键的索引 4.1.6 数据修改期间的索引维护 习题 4.2 辅助索引 4.2.1 辅助索引的...

    java 多线程设计模式 进程详解

    第四章 等待和通知 返回到银行的例子 等待和通知 wait()、notify()和notifyAll() wait()和sleep() 线程中断 静态方法(有关同步的细节) 总结 第五章 Java线程编程的例子 数据结构和容器 简单的同步...

    Visual C#2010 从入门到精通(Visual.C#.2010.Step.By.Step).完整去密码锁定版 I部分

    第4章 使用决策语句 65 4.1 声明布尔变量 65 4.2 使用布尔操作符 66 4.2.1 理解相等和关系操作符 66 4.2.2 理解条件逻辑操作符 66 4.2.3 短路求值 67 4.2.4 操作符的优先级和结合性总结 68 4.3 使用if语句来...

    精通sql结构化查询语句

    3.3.1 修改数据库语法 3.3.2 使用SQL语句修改数据库 3.4 管理数据库 3.4.1 扩充与压缩数据库 3.4.2 导入与导出数据 3.4.3 数据库的备份与恢复 3.4.4 使用sp_helpdb查看数据库信息 3.5 小结第4章 数据表的相关操作 ...

    javaSE代码实例

    第4章 流程控制——Java世界的航行舵手 42 4.1 if条件语句 42 4.1.1 简略形式 42 4.1.2 完全形式 43 4.1.3 语句的嵌套 43 4.2 switch多分支语句 45 4.2.1 基本语法 45 4.2.2 合法的判断表达式 46 ...

    Java开发实战1200例(第1卷).(清华出版.李钟尉.陈丹丹).part3

    第4章 字符串处理技术 75 4.1 格式化字符串 76 实例060 把数字格式化为货币字符串 76 实例061 格式化当前日期 77 实例062 货币金额大写格式 78 实例063 String类格式化当前日期 80 实例064 字符串大小写转换 82 实例...

    python cookbook(第3版)

    第四章:迭代器与生成器 4.1 手动遍历迭代器 4.2 代理迭代 4.3 使用生成器创建新的迭代模式 4.4 实现迭代器协议 4.5 反向迭代 4.6 带有外部状态的生成器函数 4.7 迭代器切片 4.8 跳过可迭代对象的开始部分 ...

    Go程序设计语言

    563.6 常量 563.6.1 常量生成器iota 573.6.2 无类型常量 59[0第0]4章 复合数据类型 614.1 数组 614.2 slice 634.2.1 append函数 664.2.2 slice就地修改 694.3 map 714.4 结构体 764.4.1 结构体字面量 784...

    Visual C++程序开发范例宝典(光盘) 第四部分

    第4章 多媒体技术 4.1 动画 实例119 利用Image控件制作小动画 实例120 透明的Flash动画 实例121 播放GIF动画 实例122 播放AVI动画 实例123 播放VCD 4.2 制作与播放音频 实例124 可以选择播放曲目的CD播放器...

    Spring3.x企业应用开发实战(完整版) part1

    第4章 在IoC容器中装配Bean 4.1 Spring配置概述 4.1.1 Spring容器高层视图 4.1.2 基于XML的配置 4.2 Bean基本配置 4.2.1 装配一个Bean 4.2.2 Bean的命名 4.3 依赖注入 4.3.1 属性注入 4.3.2 构造函数注入 4.3.3 工厂...

    Spring.3.x企业应用开发实战(完整版).part2

    第4章 在IoC容器中装配Bean 4.1 Spring配置概述 4.1.1 Spring容器高层视图 4.1.2 基于XML的配置 4.2 Bean基本配置 4.2.1 装配一个Bean 4.2.2 Bean的命名 4.3 依赖注入 4.3.1 属性注入 4.3.2 构造函数注入 4.3.3 工厂...

    Oracle 9i&10g编程艺术:深入数据库体系结构(全本)含脚本

    第4章 内存结构 113 4.1 进程全局区和用户全局区 113 4.1.1 手动PGA内存管理 114 4.1.2 自动PGA内存管理 121 4.1.3 手动和自动内存管理的选择 131 4.1.4 PGA和UGA小结 132 4.2 系统全局区 133 4.2.1 固定SGA ...

    疯狂JAVA讲义

    第4章 流程控制和数组 71 4.1 顺序结构 72 4.2 分支结构 72 4.2.1 if条件语句 72 4.2.2 switch分支语句 76 4.3 循环结构 78 4.3.1 while循环语句 78 4.3.2 do while循环语句 79 4.3.3 for循环 80 4.3.4 ...

Global site tag (gtag.js) - Google Analytics