文章来自达梦技术社区http://bbs.dameng.com/
摘要:在大型商业数据库的系统中,多线程机制是一种常用的提高系统并发吞吐能力的手段之一,但随之也会带来很多的问题:线程的切换、资源的竞争,如果处理的不得当,将导致系统资源的大量浪费,严重的将会产生死锁,而这些问题将最终导致系统执行效率的严重降低。本文将针对这些问题探讨DM是如何处理线程的调度及如何进行并发同步控制。
DM和其他主流商业数据库:Oracle, Microsoft SQL Server, Sybase都采用多线程机制。采用多线程的模式,能用较少的线程管理大量的用户进程;并且,线程是动态可调整的,当用户数增加时, 线程也会阶段性地自动增加;当用户数减少时,线程也会自动减少。多线程结构,大大降低了数据库对系统资源的占用,提高了系统资源的利用率。
但与Oracle采用多进程多线程多进程体系结构不同的是,DM采用单进程多线程体系结构,用户请求都由一个进程来进行总的调度和管理,并以多线程方式执行用户的请求。这种架构比多进程多线程架构所需内存开销较少,系统资源占用也要少的多,其对系统资源的利用率比采用多进程方式要高。
1. DM多线程简介
DM利用操作系统提供的机制建立了多种线程,部分线程的数目还可以根据配置文件设置。DM的线程主要有:控制台线程、工作线程、I/O 线程、会话线程、日志线程、连接监听线程、日志线程、检查点线程、死锁监控线程等。其中工作线程和I/O 线程的个数可以在配置文件中设置。各线程之间相互配合、协同工作,共同完成客户的请求,如图1所示。
图1 DM 总体结构
2. DM线程同步机制
在DM数据库内部有许多资源:系统缓冲区、全局变量、内存、文件系统、部分内存对象(数据字典、B树)等,它们为多个线程所共享,为了实现数据的最大共享。DM采用适当的编程手段等技术实现多线程协同工作,保证系统效率。
对于线程同步问题常用的方法是利用操作系统提供的同步机制,例如在Windwos平台下的Critical Section、Mutex、Semaphore、Event Object等,DM通过自己实现的线程读写锁及线程等待数组等方法来解决线程同步问题,而不是完全依靠操作系统提供的同步机制。这样做的好处就是能够最大限度的提高速度,减少因同步机制带来的性能损失。
下面就介绍下DM实现线程并发控制的常用技术方法:
(1) 封装操作系统的临界区
/** os_mutex_struct: WIN32平台下多线程操作的互斥结构 */struct os_mutex_struct { os_thread_id_t owner; /** owner: 拥有资源的当前线程ID */ ulint level; /** level: 互斥量的级别 */ dmbool type; /** type: 互斥量的类型*/ ulint lock_word; /** lock_word: 锁的类型 */ dmbool waiting; /** waiting: 等待标志 */ CRITICAL_SECTION cs; /** cs: 临界区对象 */ }; |
DM对单个对象的互斥访问权是通过结构体os_mutex_struct来实现,对于每一个共享的系统资源均有一个os_mutex_struct变量,对该资源的读写访问都必须放在os_mutex_enter和os_mutex_exit两个函数之间,这样就能保证资源在同一时间内只能够被一个线程处理。
os_mutex_struct结构体封装了操作系统的临界区对象,而不是互斥对象,临界区的行为特性与互斥对象相同,但是临界区属于用户方式对象,而互斥对象则属于内核对象。这意味着临界区的运行速度要比互斥对象要快,不需要进行调用方式的转换。同时,使用临界区的问题也是显而易见的,尤其在对多个对象进行同步控制时,很容易陷入死锁状态。
(2) 实现线程读写锁和线程等待队列
/**rw_lock_struct:定义rw锁结构*/ DM_LIST_NODE_T(rw_lock_t) list; /**rw锁链表*/ |
DM对系统资源的数目有多个时(如:系统缓冲区和B树内存对象),为了提高并发度,采用了线程读写锁及等待队列来实现同步控制。 对于这种资源每一个都含有一个rw_lock_struct变量,而系统有一个全局等待队列wait_array_struct变量global_wait_array,此变量含有一个wait_cell_struct结构的数组(数组大小可以通过ini文件来设置大小,来调整系统运行效率),每一个数组单元wait_cell_struct内储存着一个等待线程的相关信息,包含一个rw_lock_struct锁对象,一个等待线程的ID,还有一个操作系统的同步对象event(用来阻塞和唤醒等待的线程)等变量。
这些资源的读访问,通过调用rw_lock_s_lock函数对资源的rw_lock上共享锁,如果不能上,线程则挂起,加到线程的等待队列中。当某个线程访问完资源时,通过调用rw_lock_s_lock_unlock函数释放rw_lock中的线程级共享锁,如果rw_lock中的共享锁均已释放,而且在等待队列中还有等待该锁的写线程,则激发相应的写线程对其上锁。
这些资源的写访问,通过调用rw_lock_x_lock函数对资源的rw_lock上排它锁,如果不能上,线程则挂起,加到线程的等待队列中。当某个线程访问完资源时,通过调用rw_lock_x_lock_unlock函数释放rw_lock中的线程级排它锁,如果在等待队列中还有等待该锁的写线程或读线程,则激发相应的线程对其上锁。
锁的相容性矩阵如下表:
S锁 | X锁 | |
S锁 | + | - |
X锁 | - | - |
表1 线程锁的相容性矩阵
3. DM解决死锁的方法
通过标题2介绍的内容,DM虽然实现了多线程对资源的同步访问问题,但是这种机制并不能保证另一个严重的问题——死锁不会发生。因此,DM采用死锁预防的方法防止线程之间的死锁,下面详细介绍DM所采用的方法
a、DM将系统资源分类并赋予优先级,通过编写程序规定了各工作线程只会按照优先级从低到高的顺序申请资源。当某种系统资源的数目只有一个(如:全局变量)时,只要保证在占有该全局变量时只会申请高优先级的系统资源,同时保证退出占用资源的顺序,就不会出现死锁。资源优先级的划分表如下:
在下列系统资源列表中,优先级的数值越大表明该资源优先级越低:
资源名称 | 优先级 |
字典缓冲 | 10000(RM_LEVEL_DICT_CACHE) |
会话数据结构(sess3_sys) | 9600(RM_LEVEL_SESS_SYS) |
二级索引内结点、叶结点 | 6600(RM_LEVEL_PAGE_2ND_INDEX_INNER) |
聚簇索引内结点、叶结点 | 6400(RM_LEVEL_PAGE_DATA_INDEX_INNER) |
数据库结构(fil_area_t) | 6200(RM_LEVEL_DB) |
文件组头块(第0块)、盘区描述块、段控制信息块 | 6000 |
系统锁信息结构(lock3_sys),用于记录表锁、元组锁、区间锁 | 5500 |
3000(RM_LEVEL_BUF_POOL) | 3000(RM_LEVEL_BUF_POOL) |
系统锁信息结构(lock3_sys),用于记录表锁、元组锁、区间锁 | 2000(RM_LEVEL_FIL_SYS) |
表2 系统资源系统优先级列表
b、当某种系统资源的数目只有一个(如:全局变量)时,只要保证在占有该全局变量时只会申请高优先级的系统资源,同时保证退出占用资源的顺序,就不会出现死锁。但是并非所有的系统资源都是这样。某种资源的数目有多个(如:系统缓冲区和B树内存对象),为了提高并发度,同时为了防止相同优先级的资源之间的死锁,DM采用了线程读写锁,同时规定在短暂的时间内每次只能访问一棵B树,防止在一个步骤内同时占用并能修改两个B树,并且每次对B树的访问又总是按照一定的次序进行的,从而有效防止线程之间的死锁。
以上是对DM多线程并发控制机制的一些介绍,当然这里只讲述了部分内容,还有很多具体的内容和细节没有涉及到,但是对理解DM多线程并发控制机制的思想和以后工作中可能遇到的问题还是有所帮助的。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/23392679/viewspace-627960/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/23392679/viewspace-627960/