一、锁
在书中,我们实现一个锁的背景是:对已经写好的单线程的双向链表做一个改进,使得它可以支持单线程和多线程两种方式。并且满足以下要求:
要求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.h 和 dlist.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.h和locker_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.h和locker_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);
相关推荐
本部分主要涵盖了第三章“进程”、第四章“中断和异常”以及第五章“内核同步”三个关键章节,以下是这些章节的详细知识点。 ### 第三章:进程 1. **进程定义**:在Linux中,进程是执行中的程序实例,具有独立的...
第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 辅助索引的...
第四章 等待和通知 返回到银行的例子 等待和通知 wait()、notify()和notifyAll() wait()和sleep() 线程中断 静态方法(有关同步的细节) 总结 第五章 Java线程编程的例子 数据结构和容器 简单的同步...
《云计算》第三版课程配套PPT课件详细介绍了云计算的核心概念和技术,特别是针对Google云计算的原理与应用进行了深入探讨。本课程涵盖了多个重要章节,旨在帮助学生和专业人士掌握云计算的基础和高级主题。以下是对...
3. 子查询:理解如何在查询中嵌套查询,以实现更复杂的逻辑。 4. 分组与聚合函数:GROUP BY和HAVING子句用于分组数据,COUNT、SUM、AVG、MIN、MAX等函数用于计算统计信息。 5. 视图:创建和使用视图以简化查询并提供...
第4章“中断和异常”主要讲解了Linux内核如何处理硬件事件和错误。中断是计算机硬件向CPU发出的信号,表明有紧急事件需要处理,如I/O操作完成或硬件故障。异常则通常是因为软件执行错误或非法指令引发。本章详细介绍...
- 第四章讲述了线程间的通信机制,包括wait和notify方法,以及条件变量。 5. **最小同步技术** - 第五章讨论了是否可以避免同步以及原子变量、ThreadLocal变量的使用。 6. **高级同步主题** - 第六章涵盖了同步...
3. OSCtxSw():上下文切换函数,用于在任务之间切换。在某些处理器上,可能涉及保存和恢复寄存器,以及调整堆栈指针。 4. OSSchedLock() 和 OSSchedUnlock():用于锁定和解锁调度器。在某些处理器上,可能涉及到...
第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语句来...
第四章:连接与子查询 深入理解JOIN操作,包括内连接、外连接和自连接,以及子查询在查询中的应用,如嵌套查询、存在子查询和关联子查询。 第五章:数据更新与插入 讲解如何使用UPDATE语句修改表中的数据,以及...
《第二章 双闭环直流调速系统》 双闭环直流调速系统是电力拖动与运动控制领域中广泛应用且性能优异的控制方案。本章主要探讨其控制策略、特性优势以及设计方法,对于理解各类交流和直流电力拖动自动控制系统至关...
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章 数据表的相关操作 ...
第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 ...
6. **实现数据的完整性**(第四章):这部分内容会介绍实体完整性、参照完整性和用户定义的完整性,以及如何通过约束(如主键、外键、唯一性约束等)来确保数据质量。 7. **实现触发器**(第十章):触发器是一种在...
第4章 字符串处理技术 75 4.1 格式化字符串 76 实例060 把数字格式化为货币字符串 76 实例061 格式化当前日期 77 实例062 货币金额大写格式 78 实例063 String类格式化当前日期 80 实例064 字符串大小写转换 82 实例...
在`第4章数据库对象.sql`中,可能讲解了Oracle中的表、视图、索引、存储过程、触发器等数据库对象的创建、修改和删除。理解这些对象对于数据库设计和管理至关重要。例如,表是数据的主要存储结构,视图提供虚拟表的...
第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 ...