rn_xtcxyczjh-7 并发[线程3 嵌套锁与装饰模式]

2015.10.30
读xtcxyczjh(系统程序员成长计划)—- 学习程序设计方法。
个人笔记对应代码保存地址:y15m10d30
作者实现的是递归锁。

1. 需求简述

实现一个嵌套锁,要求如下:
- 嵌套锁仍然兼容Locker接口。
- 嵌套锁的实现不依赖于特定平台。

2. 准备

读《xtcxyczjh》P.46-P.48:
- 嵌套加锁造成的死锁:在同一线程中,连续加锁(程序会被阻塞在第二次线程加锁处)。
- 装饰模式:不改变对象的本质(接口)的前提下,给对象(锁)添加附加的功能(对一把锁进行装饰,不改变它的接口,但给它加上嵌套的功能)。

3. 代码

(1) 分析

  • 嵌套锁的实现算法(P.46 —- 可能会导致线程中无必要加锁的代码加锁,如父函数加锁后调用前面有一大段代码都不用加锁的子函数)。
  • 与平台相关的功能模块要用回调函数来抽象,如获取当前线程ID的函数、创建线程嵌入锁的函数等。
  • 描述嵌入锁(装饰锁)的数据结构(保存线程的ID、计数、线程锁接口、获取线程ID函数指针等元素)。

(2) 实现

[1]装饰锁接口
因为不能改变线程锁的接口,故而需要另定义一个数据结构类型来描述“嵌入锁”以及“获取线程ID回调函数“等功能。利用C语言结构体实现变长数组的机制,将新定义的数据结构加到线程锁接口的对象中,这样就能够实现装饰模式

描述装饰锁的数据结构。

/* locker.h */
/* 定义不依赖于平台的线程锁接口 */
#ifndef LOCKER_H
#define LOCKER_H
//……
//锁接口
struct _Locker {
    LockerLockFuncT     lock;
    LockerUnlockFuncT   unlock;
    char                dcrt[0];
};

//装饰锁接口
typedef int (*pGetThreadSelfIDFuncT)(void);
typedef struct _DcrtLockT {
    int                     owner;      //拥有锁的线程ID
    int                     count;      //加锁计数器
    pGetThreadSelfIDFuncT   tid_self;   //指向获取线程ID函数的函数指针
}DcrtLockT;
#endif

[2] 嵌入锁函数
创建嵌入锁函数。在cpthread.c中,将create_thread_locker函数修改为以下内容:

/* cpthread.c */
/* 调用POSIX thread库中的函数定义线程线程相关函数 */
//……

//用户自定实现线程锁初始化函数
Locker *create_thread_nest_locker(void)
{
    //在这里添加您初始化线程嵌入锁的代码
    Locker  *locker = NULL;

    locker  = (Locker *)malloc(sizeof(Locker) + sizeof(DcrtLockT));
    if (NULL != locker) {

        DcrtLockT   *pdcrt = NULL;

        pdcrt   = (DcrtLockT *)locker->dcrt;

        locker->lock    = lock_nest_thread;
        locker->unlock  = unlock_nest_thread;

        pdcrt->owner    = 0;
        pdcrt->count    = 0;
        pdcrt->tid_self = get_thread_id;
    }

    //初始化此平台下的线程锁
    lock_nest_init();

    return locker;
}

locker = (Locker *)malloc(sizeof(Locker) + sizeof(DcrtLockT));语句用DcrtLockT装饰了锁对象locker。装饰模式最有用的地方在于,它给单个对象增加功能,但不是影响调用者,即使加了多级装饰,调用者也不用关心。

根据创建线程锁函数的更改,修改其它地方的代码:

/* cpthread.h */
/* 定义线程相关,或声明定义在cpthread.c中的调用POSIX thread库的函数 */

#ifndef CPTHREAD_H
#define CPTHREAD_H
//……
Locker *create_thread_nest_locker(void);
//……
#endif


/* cpthread.c */
/* 调用POSIX thread库中的函数定义线程线程相关函数 */
//……
//用户自定义实现获取线程ID函数
static int get_thread_id(void)
{
    //在这里添加获取当前线程ID的代码
    return pthread_self();
}
//……

lock_nest_thread和unlock_nest_thread分别为线程加锁和解锁函数。

加锁函数。

/* cpthread.c */
/* 调用POSIX thread库中的函数定义线程线程相关函数 */
//……

//用户自定义初始化线程锁函数
static RetT lock_nest_thread(Locker *locker)
{
    //在这里定义你的线程加锁代码
    int ret = 0;
    DcrtLockT   *pdcrt_locker   = (DcrtLockT *)locker->dcrt;

    if (pdcrt_locker->owner == pdcrt_locker->tid_self()) {
        pdcrt_locker->count++;
    }else {
        ret = sem_init(&mutex, 0, 1);
        pdcrt_locker->count = 1;
        pdcrt_locker->owner = pdcrt_locker->tid_self();
    }

    return ret == 0 ? RET_OK : RET_FAIL;
}
//……

如果当前线程已经加锁,只是增加加锁计数,否则就加锁。

解锁函数。

/* cpthread.c */
/* 调用POSIX thread库中的函数定义线程线程相关函数 */
//……
//用户自定义实现的线程加锁函数
static RetT unlock_nest_thread(Locker *locker)
{
    //在这里定义你的线程加锁代码
    int ret = 0;
    DcrtLockT   *pdcrt_locker   = (DcrtLockT *)locker->dcrt;
    return_val_if_p_invalid(pdcrt_locker->owner == pdcrt_locker->tid_self(), RET_FAIL);

    pdcrt_locker->count--;
    if (pdcrt_locker->count == 0) {
        pdcrt_locker->owner = 0;
        ret = sem_wait(&mutex);
    }

    return ret == 0 ? RET_OK : RET_FAIL;
}
//……

只有当前线程加的锁才能解锁,先减少加锁计数,计数为0时才真正解锁,否则直接返回。

修改原调用create_thread_locker函数的代码:

/* main.c */
/* 包含C程序入口 */
#include <stdio.h>
#include "cpthread.h"
#include <assert.h>
#include "autotest.h"

int main(void)
{
    DlistManageT    *pdl = NULL;
    unsigned int    dlen = 10;

    single_thread_test(&pdl, dlen, NULL);
    free_dlist(pdl);

    pdl = NULL;
    multi_thread_test(&pdl, dlen, create_thread_nest_locker);
    free_dlist(pdl);
    return 0;
}

因为本次代码都是通过装饰模式来完成的,关于线程锁接口以及双向链表程序都没有被改动过。所以在用户角度下修改代码后,可直接在与Makefile以及程序所在同目录下使用make命令。然后执行./all程序得到以下执行结果:
rn_xtcxyczjh-7 并发[线程3 嵌套锁与装饰模式]_第1张图片
姑且算它运行正常。

读《xtcxyczjh》-Part-VII pnote over.
[2015.10.30-18:15]

你可能感兴趣的:(c,程序设计方法)