『 Linux 』线程安全的单例模式,自旋锁与读写锁

37 篇文章 2 订阅
9 篇文章 0 订阅


单例模式

请添加图片描述

单例模式是一种创建型设计模式,其主要目的是确保一个类只有一个实例,并提供一个全局访问点来访问该实例;

这在需要严格控制如何及合适访问某个唯一资源型下有一定作用;

单利模式的主要特点为如下:

  • 私有构造函数

    单例模式通常要将构造私有化,以保证无法直接通过该类实例化出对应的对象;

    只能通过该类提供对应的接口来实例化整个对象,确保只有一个实例;

  • 私有静态实例

    私有静态实例保证实例只能在类内部进行访问,外部代码无法直接操作这个实例;

    可防止外部代码以外或恶意修改改实例;

    由于静态实例是私有的,可保证无论创建多少类的对象,静态成员始终只有一份;

    在单例模式中以该特性确保整个程序中只有一个这样的实例;

  • 公有静态函数

    公有静态函数提供全局访问点来获取唯一的实例;

    确保只允许外部代码通过该静态函数来访问类中唯一的实例;

    一般情况下如果该实例还未存在时该方法将会创建一个新的实例,如果已存在则直接返回现有实例;

单例模式的优点:

  • 保证一个类只有一个实例从而减少内存开销
  • 避免对资源的多重占用
  • 提供了对唯一实例的全局访问点

懒汉模式与饿汉模式

请添加图片描述

懒汉模式与饿汉模式属于单利模式加载方式的其中一种;

  • 懒汉加载模式

    懒汉模式指的是在进程启动时不立马加载实例,而是等到需要用到实例的时候再对实例进行加载;

    该加载方式由于单例在进程启动时未被加载从而能够有效提升进程整体的加载速度;

    但如果多个执行流同时在需要的时候加载实例时可能涉及到临界资源竞争问题,即懒汉模式不是线程安全的,故在以懒汉模式进行设计时应该要确保线程安全,如各个线程应通过互斥锁保持其互斥关系以避免临界资源竞争的问题;

    class SingletonPattern {
     public:
      // 获取单例实例的静态方法
      static SingletonPattern* getSP() {
        // 如果实例不存在,则创建一个新实例
        if (!sp_) sp_ = new SingletonPattern();
        // 返回单例实例
        return sp_;
      }
    
     private:
      // 私有构造函数,防止外部直接创建实例
      SingletonPattern() {
        // 无限循环,持续输出信息并暂停1秒
        while (1) {
          cout << "SingletonPattern is running..." << endl;
          sleep(1);
        }
      }
    
     private:
      // 静态成员变量,用于存储单例实例
      static SingletonPattern* sp_;
    };
    
    // 静态成员变量初始化为nullptr
    SingletonPattern* SingletonPattern::sp_ = nullptr;
    
    int main() {
      // 主程序开始前暂停3秒
      sleep(3);
      // 获取单例实例
      SingletonPattern* sp = SingletonPattern::getSP();
      return 0;
    }
    
    

    在这个例子中主函数开始时程序暂停3s;

    然后调用getSP()以触发实例的创建,一但实例被创建将开始进入构造函数;

  • 饿汉加载模式

    饿汉模式指的是在进程启动时直接将实例进行加载;

    饿汉模式设计是天然线程安全的,不需要额外的同步机制;

    但由于实例是在进程启动时加载,对应的进程启动的时间也会变慢;

    同时如果实例从未被使用则会造成资源浪费;

    // 单例模式类
    class SingletonPattern {
     public:
      // 获取单例实例的静态方法
      static SingletonPattern* getSP() {
        // 如果实例不存在,则创建一个新实例
        if (!sp_) sp_ = new SingletonPattern();
        // 返回单例实例
        return sp_;
      }
    
     private:
      // 私有构造函数,防止外部直接创建实例
      SingletonPattern() {
        // 构造函数的实现(此处为空)
      }
    
      // 静态成员变量,用于存储单例实例
      static SingletonPattern* sp_;
    };
    
    // 静态成员变量的初始化
    // 这里直接调用getSP(),确保在程序启动时就创建实例
    SingletonPattern* SingletonPattern::sp_ = SingletonPattern::getSP();
    
    // 主函数
    int main() {
      // 主函数为空,但单例实例已在程序启动时创建
      return 0;
    }
    

    在这个例子中,由于实例的加载是静态的,故主函数未被加载时单例已经被加载;

[基于懒汉模式加载的单例模式线程池参考代码 - gitee]


自旋锁

请添加图片描述

自旋锁是一种用于多线程同步的低级锁机制;

当一个线程尝试读取一个被其他线程持有的自旋锁时,它将会一直循环检查锁是否可用而不是挂起等待;

  • 互斥锁与自旋锁的区别

    当一个线程试图使用一个被其他线程占用的互斥锁时该线程将进入阻塞等待,直到互斥锁被解除占用时将会唤醒这个线程;

    当一个线程试图使用一个被其他线程占用的自旋锁时该线程不会进入阻塞等待,而是不停尝试申请锁资源;

POSIX线程库提供了一个自旋锁为pthread_spinlock_t类型;

当需要使用该库的自旋锁时必须声明一个该类型的锁变量;

  • 自旋锁的初始化与销毁

    自旋锁的初始化与销毁通常使用pthread_spin_init()pthread_spin_destroy();

    PROLOG
           This  manual  page is part of the POSIX Programmer's Manual.  The Linux implementation of this interface may differ (con‐
           sult the corresponding Linux manual page for details of Linux behavior), or the  interface  may  not  be  implemented  on
           Linux.
    
    NAME
           pthread_spin_destroy, pthread_spin_init - destroy or initialize a spin lock object (ADVANCED REALTIME THREADS)
    
    SYNOPSIS
           #include <pthread.h>
    
           int pthread_spin_destroy(pthread_spinlock_t *lock);
           int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
    
    RETURN VALUE
           Upon successful completion, these functions shall return zero; otherwise, an error number shall be returned  to  indicate
           the error.
    
    

    这两个函数调用成功时返回0,调用失败时返回一个错误码(非0);

    两个函数所传递的第一个参数都为一个pthread_spinlock_t *类型的自旋锁指针;

    pthread_spin_init()函数的第二个参数pshared设置该自旋锁是否为共享属性:

    • PTHREAD_PROCESS_PRIVATE

      表示自旋锁只能在初始化它的进程内线程之间的共享;

    • PTHREAD_PROCESS_SHARED

      表示自旋锁可以在多个进程中的线程之间共享;

    pthread_spin_init()函数用于初始化自旋锁,pthread_spin_destroy()用于释放该自旋锁;

  • 自旋锁的锁定

    通常使用pthread_spin_lock()锁定自旋锁;

    PROLOG
           This  manual  page is part of the POSIX Programmer's Manual.  The Linux implementation of this interface may differ (con‐
           sult the corresponding Linux manual page for details of Linux behavior), or the  interface  may  not  be  implemented  on
           Linux.
    
    NAME
           pthread_spin_lock, pthread_spin_trylock - lock a spin lock object (ADVANCED REALTIME THREADS)
    
    SYNOPSIS
           #include <pthread.h>
    
           int pthread_spin_lock(pthread_spinlock_t *lock);
           int pthread_spin_trylock(pthread_spinlock_t *lock);
    
    RETURN VALUE
           Upon  successful  completion, these functions shall return zero; otherwise, an error number shall be returned to indicate
           the error.
    

    其中pthread_spin_lock()函数为尝试获取一个自旋锁,如果该自旋锁被其他线程持有,则一直自旋直至成功获取锁;

    pthread_spin_trylock()函数则是尝试获取一个自旋锁,如果该锁被其他线程持有则立即返回不进行自旋;

    两个函数的参数都为传递一个pthread_spinlock_t *的自旋锁对象指针;

    当函数调用成功时返回0,失败时则返回一个错误码;

  • 自旋锁的解锁

    通常使用pthread_spin_unlock()对自旋锁进行解锁;

    PROLOG
           This  manual  page is part of the POSIX Programmer's Manual.  The Linux implementation of this interface may differ (con‐
           sult the corresponding Linux manual page for details of Linux behavior), or the  interface  may  not  be  implemented  on
           Linux.
    
    NAME
           pthread_spin_unlock - unlock a spin lock object (ADVANCED REALTIME THREADS)
    
    SYNOPSIS
           #include <pthread.h>
    
           int pthread_spin_unlock(pthread_spinlock_t *lock);
    
    DESCRIPTION
           The  pthread_spin_unlock()  function  shall  release  the  spin  lock  referenced  by  lock  which  was  locked  via  the
           pthread_spin_lock() or pthread_spin_trylock() functions. The results are undefined if the lock is not held by the calling
           thread. If there are threads spinning on the lock when pthread_spin_unlock() is called, the lock becomes available and an
           unspecified spinning thread shall acquire the lock.
    
           The results are undefined if this function is called with an uninitialized thread spin lock.
    
    RETURN VALUE
           Upon successful completion, the pthread_spin_unlock() function shall return zero; otherwise, an  error  number  shall  be
           returned to indicate the error.
    
    

    该函数用于解锁一个自旋锁,当函数调用成功时返回0,调用失败时则返回一个错误码;

    其中参数传递表示传递一个pthread_spinlock_t *的自旋锁对象指针;


读写锁

请添加图片描述

读写锁也被称为共享锁/独占锁,允许多个线程同时读取共享资源,但在写入时只允许一个线程访问;

读写锁分为两种状态:

  • 读锁(共享锁)

    表示多个线程可以同时持有读锁,当线程持有读锁时其他线程无法再获取写锁;

  • 写锁(独占锁)

    表示只有一个线程可以持有写锁,同时此时不能有任何读锁;

读者写者问题本质上也可以理解为一个生产者消费者模型的问题,具有两个角色,三种关系和一个交易场所;

  • 两个角色

    读者与写者;

  • 三种关系

    • 写者与写者的互斥关系

    • 写者与读者的互斥与同步关系

    • 读者与读者的共享关系

      读者与读者的共享关系主要是因为其只对数据进行读取,不对数据进行覆盖或修改等操作;

  • 一个交易场所

    指的是以一个特定结构的内存空间(临界区);

POSIX线程库中同样提供了读写锁;

在该库中的读写锁类型为pthread_rwlock_t,当需要使用读写锁时必须使用该类型声明一个读写锁变量;

  • 读写锁的初始化与销毁

    通常使用pthread_rwlock_init()pthread_rwlock_destroy()对读写锁进行初始化与销毁;

    PROLOG
           This  manual  page is part of the POSIX Programmer's Manual.  The Linux implementation of this interface may differ (con‐
           sult the corresponding Linux manual page for details of Linux behavior), or the  interface  may  not  be  implemented  on
           Linux.
    
    NAME
           pthread_rwlock_destroy, pthread_rwlock_init - destroy and initialize a read-write lock object
    
    SYNOPSIS
           #include <pthread.h>
    
           int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
           int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
                  const pthread_rwlockattr_t *restrict attr);
    
    RETURN VALUE
           If  successful,  the  pthread_rwlock_destroy() and pthread_rwlock_init() functions shall return zero; otherwise, an error
           number shall be returned to indicate the error.
    
           The [EBUSY] and [EINVAL] error checks, if implemented, act as if they were performed immediately at the beginning of pro‐
           cessing  for  the  function  and  caused an error return prior to modifying the state of the read-write lock specified by
           rwlock.
    
    

    其中两个函数的第一个参数都为pthread_rwlock_t *类型的读写锁指针变量,以初始化或销毁该读写锁;

    两个函数调用成功时都会返回0,调用失败时则会返回一个非0错误码;

    pthread_rwlock_init()的第二个参数attr表示需要传递一个读写锁属性对象的指针,若是传递nullptr表示使用默认属性;

    同时也可使用PTHREAD_RWLOCK_INITIALIZER宏来静态初始化读写锁(静态初始化的读写锁可不需要调用pthread_rwlock_init()pthread_rwlock_destroy()对读写锁进行初始化与释放);

  • 写者加锁

    通常使用pthread_rwlock_wrlock()函数用于读者加锁;

    PROLOG
           This  manual  page is part of the POSIX Programmer's Manual.  The Linux implementation of this interface may differ (con‐
           sult the corresponding Linux manual page for details of Linux behavior), or the  interface  may  not  be  implemented  on
           Linux.
    
    NAME
           pthread_rwlock_trywrlock, pthread_rwlock_wrlock - lock a read-write lock object for writing
    
    SYNOPSIS
           #include <pthread.h>
    
           int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
           int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
    
    RETURN VALUE
           The pthread_rwlock_trywrlock() function shall return zero if the lock for writing on the read-write  lock  object  refer‐
           enced by rwlock is acquired. Otherwise, an error number shall be returned to indicate the error.
    
           If  successful,  the  pthread_rwlock_wrlock() function shall return zero; otherwise, an error number shall be returned to
           indicate the error.
    

    其中pthread_rwlock_wrlock()表示线程尝试获取一个写锁,如果该读写锁被其他线程(读锁或是写锁)占用则阻塞;

    pthread_rwlock_trywrlock()表示线程尝试获取一个写锁,如果该读写锁被其他线程占用则返回不进行阻塞;

    两个函数所传递的参数pthread_rwlock_t *rwlock表示传递一个读写锁对象的指针;

    两个函数调用成功时都会返回0,调用失败时则会返回一个非0错误码;

  • 读者加锁

    通常使用pthread_rwlock_rdlock()函数用于读者加锁;

    PROLOG
           This  manual  page is part of the POSIX Programmer's Manual.  The Linux implementation of this interface may differ (con‐
           sult the corresponding Linux manual page for details of Linux behavior), or the  interface  may  not  be  implemented  on
           Linux.
    
    NAME
           pthread_rwlock_rdlock, pthread_rwlock_tryrdlock - lock a read-write lock object for reading
    
    SYNOPSIS
           #include <pthread.h>
    
           int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
           int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
    
    RETURN VALUE
           If  successful,  the  pthread_rwlock_rdlock() function shall return zero; otherwise, an error number shall be returned to
           indicate the error.
    
           The pthread_rwlock_tryrdlock() function shall return zero if the lock for reading on the read-write  lock  object  refer‐
           enced by rwlock is acquired. Otherwise, an error number shall be returned to indicate the error.
    

    其中pthread_rwlock_rdlock()表示线程尝试获取一个读锁,如果该读写锁被其他线程以写锁占用则阻塞,否则与其他持有读锁的线程共享该写锁;

    pthread_rwlock_tryrdlock()表示线程尝试获取一个读锁,如果该读写锁被其他线程以写锁占用则返回不进行阻塞,否则与其他持有读锁的线程共享该锁;

    两个函数所传递的参数pthread_rwlock_t *rwlock表示传递一个读写锁对象的指针;

    两个函数调用成功时都会返回0,调用失败时则会返回一个非0错误码;

  • 读写锁的解锁

    通常使用pthread_rwlock_unlock()函数来解锁读写锁;

    PROLOG
           This  manual  page is part of the POSIX Programmer's Manual.  The Linux implementation of this interface may differ (con‐
           sult the corresponding Linux manual page for details of Linux behavior), or the  interface  may  not  be  implemented  on
           Linux.
    
    NAME
           pthread_rwlock_unlock - unlock a read-write lock object
    
    SYNOPSIS
           #include <pthread.h>
    
           int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
    
    RETURN VALUE
           If  successful,  the  pthread_rwlock_unlock() function shall return zero; otherwise, an error number shall be returned to
           indicate the error.
    

    其中传递的参数表示传递一个需要解锁的读写锁对象指针;

    该函数调用成功时返回0,失败时返回一个错误码;

通常情况下在使用该锁时读者的数量必定大于写者,且当读者在读的时候写者获取读写锁时将进行阻塞,这个现象可能导致写者长时间得不到锁资源所产生饥饿问题,这个现象被称为读者优先;

  • 6
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dio夹心小面包

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值