• 系统程序员成长计划并发(二)(下)


    转载时请注明出处和作者联系方式
    文章出处:http://www.limodev.cn/blog
    作者联系方式:李先静 <xianjimli at hotmail dot com>

    面 对这个需求,一些初学者可能有点蒙了。以前在学校的时候,对于课本后面的练习,我总是信心百倍,原因很简单,我确信这些练习不管它的出现方式有多么不同, 但总是与前面学过的知识有关。记得《如何求解问题—现代启发式方法》中说过,正是这种练习的方式妨碍了我们解决问题的能力,在现实中解决问题时通常没有这 么幸运。在《系统程序员成长计划》我把练习放前面,目标就是刺激读者去思考,在学习知识的同时学习解决问题的方法。

    这里我们应该怎么分析呢?要在双向链表里加锁,第一是要区分单线程和多线程,要链接同一个库,而且不能用宏来控制。第二是不能依赖于特定平台,而锁 本身恰恰又是依赖于平台的。怎么办?很明显这两个需求都要求锁的实现可以变化的:单线程版本它什么都不做,多线程版本中,不同的平台有不同的实现。

    我们要做的就是隔离变化。变化怎么隔离?前面我们已经练习过几次用回调函数来隔离变化了,所有的读者都会想到这个方法,因为锁无非是具有两个功能:加锁和解锁,我们把它抽象成两个回调函数就行了。

    这种方法是可行的。这里的情况与前面相比有点特殊:前面的回调函数都是些独立功能的函数,每个回调函数都有自己的上下文,而这里的多个回调函数具有 相关的功能,并且共享同一个上下文(锁)。其次是这里的上下文(锁)是一个对象,有自己的生命周期,完成自己的使命后就应该被销毁。

    这里我们引入接口(interface)这个术语,接口其实就是一个抽象的概念,它只定义调用者和实现者之间的契约,而不规定实现的方法。比如这里 的锁就是一个抽象的概念,它有加锁/解锁两个功能,这是调用者和实现者之间的契约。但光有这个概念不能做任何事情,只有具体的锁才能被使用。至于具体的 锁,不同的平台有不同的实现,但调用者不用关心。正因为调用者不用关心接口的实现方法,接口成了隔离变化最有力的武器。

    在这里,锁是一个接口,双向链表是锁的调用者,有基于不同方式实现的锁。通过接口,双向链表把锁的变化隔离开来:区分单线程和多线程,隔离平台相关性。在C语言中,接口的朴素定义是:一组相关的回调函数及其共享的上下文。我们看看锁这个接口怎么定义:

    struct _Locker;
    typedef struct _Locker Locker;

    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];
    };

    这里要注意三个问题:

    o 接口一定要足够抽象,不能依赖任何具体实现的数据类型。接口一旦与某个具体实现关联了,另外一种实现就会遇到麻烦。比如这里你使用了pthread_mutex_t,那你要实现一个win32下的锁怎么办呢。

    o 接口不能有create函数,但一定要有destroy函数。我们说过对象有自己的生命周期,创建它,使用它,然后销毁它。但接口只是一个概念,不可能通 过这个概念凭空创建一个对象出来,对象只能通过具体实现来创建,所以接口不应该出现create自己的函数。一旦对象被创建出来,使用者应该在不再需要它 时销毁它,在销毁对象时,如果还要知道它的实现方式才能销毁它,那就造成了调用者和实现者之间不必要的耦合,因此接口都要提供一个destroy函数,调 用者可以直接销毁它。

    o 这里的priv用来存放上下文信息,也就是具体实现需要用到的数据结构。像前面的回调函数一样,我们可以用一个void* ctx的成员来保存上下文信息。我们使用的char priv[0];技巧,有点额外的好处:只需要一次内存分配,而且可以分配刚好够用的长度(0到任意长度)。

    前面我们使用回调函数,调用时要判断回调函数是否为空,每个地方都要重复这个动作,所以我们把这些判断集中起来好了:

    static inline Ret locker_lock(Locker* thiz)
    {
    return_val_if_fail(thiz != NULL && thiz->lock != NULL, RET_INVALID_PARAMS);

    return thiz->lock(thiz);
    }

    static inline Ret locker_unlock(Locker* thiz)
    {
    return_val_if_fail(thiz != NULL && thiz->unlock != NULL, RET_INVALID_PARAMS);

    return thiz->unlock(thiz);
    }

    static inline void locker_destroy(Locker* thiz)
    {
    return_if_fail(thiz != NULL && thiz->destroy != NULL);

    thiz->destroy(thiz);

    return;
    }

    下面我们来看看基于pthread_mutex的实现:

    o 在locker_pthread.h中,提供一个创建函数。

    Locker* locker_pthread_create(void);

    o 在locker_pthread.c中,实现这些回调函数:

    定义私有数据结构:

    typedef struct _PrivInfo
    {
    pthread_mutex_t mutex;
    }PrivInfo;

    创建对象:

    Locker* locker_pthread_create(void)
    {
    Locker* thiz = (Locker*)malloc(sizeof(Locker) + sizeof(PrivInfo));

    if(thiz != NULL)
    {
    PrivInfo* priv = (PrivInfo*)thiz->priv;

    thiz->lock = locker_pthread_lock;
    thiz->unlock = locker_pthread_unlock;
    thiz->destroy = locker_pthread_destroy;

    pthread_mutex_init(&(priv->mutex), NULL);
    }

    return thiz;
    }

    实现几个回调函数:

    static Ret  locker_pthread_lock(Locker* thiz)
    {
    PrivInfo* priv = (PrivInfo*)thiz->priv;

    int ret = pthread_mutex_lock(&priv->mutex);

    return ret == 0 ? RET_OK : RET_FAIL;
    }

    我简单说一下里面几个问题:

    o malloc(sizeof(Locker) + sizeof(PrivInfo)); 前面的char priv[0]并不占空间,这是C语言新标准定义的,用于实现变长的buffer,它在这里的长度由sizeof(PrivInfo)决定。

    o PrivInfo* priv = (PrivInfo*)thiz->priv; 这里的thiz->priv只是一个定位符,实际上等于(size_t)thiz+sizeof(Locker),帮我们定位到私有数据的内存地址上。

    使用方法:

    单线程版本:

    DList* dlist = dlist_create(NULL, NULL, locker_pthread_create());

    多线程版本:

    DList* dlist = dlist_create(NULL, NULL, NULL);

    接口在软件设计中占有非常重要的地位,它是隔离变化和降低复杂度最有力的武器,差不多所有的设计模式都与接口有关。后面我们会反复的练习,这里请读者仔细体会一下。

    本节示例代码请到这里下载。


    欢迎到Linux mobile development上交流

  • 相关阅读:
    Discuz! 的编码规范
    Golang 并发编程指南
    Hyrum's Law
    从数组中将变量导入到当前的符号表
    map[interface {}]interface {} yaml文件解码
    迪基福勒检验
    约定式路由
    use of internal package github.com/gokratos/kratos/v2/internal/httputil not allowed
    See https://v8.dev/blog/mathrandom for details.
    Cast a value as a certain type
  • 原文地址:https://www.cnblogs.com/zhangyunlin/p/6167574.html
Copyright © 2020-2023  润新知