线程同步之互斥锁

作者: pro648 | 来源:发表于2021-02-21 17:46 被阅读0次
LockMind.png

这是并发控制方案的系列文章,介绍了各种锁的使用及优缺点。

  1. 自旋锁
  2. os_unfair_lock
  3. 互斥锁
  4. 递归锁
  5. 条件锁
  6. 读写锁
  7. @synchronized

OSSpinLock、os_unfair_lock、pthread_mutex_t、pthread_cond_t、pthread_rwlock_t 是值类型,不是引用类型。这意味着使用 = 会进行复制,使用复制的可能导致闪退。pthread 函数认为其一直处于初始化的内存地址,将其移动到其他内存地址会产生问题。使用copy的OSSpinLock不会崩溃,但会得到一个全新的锁。

如果你对线程、进程、串行、并发、并行、锁等概念还不了解,建议先查看以下文章:

互斥锁(mutual exclusion,缩写为 mutex)是一种并发控制方案,用于解决竞争条件(race condition)。

这篇文章包含两种实现互斥锁的方案:pthread_mutex_tNSLock

1. pthread_mutex_t

可移植操作系统接口(Portable Operating System Interface,缩写为 POSIX)是 IEEE 为在各种 UNIX 操作系统上运行软件,而定义的一系列互相关联的标准总和,X 表明其对 Unix API 的传承。

POSIX Thread 常被称为 pthreads,是 POSIX 的线程标准,定义了创建和操作线程的一套 API。通常,组合为 libpthread 库。

Pthreads 定义了一套 C 语言的函数、常量、类型,以pthread.h头文件和一个线程库实现。Pthreads API 中大致有100个函数调用,全部以pthread_开头,可以分为以下四类:

  • 线程管理:如创建线程、等待线程、查询线程状态等。
  • 互斥锁:创建、销毁、加锁、解锁、设置属性等。
  • 条件变量(Condition Variable):创建、销毁、等待、通知、设置、查询属性等操作。
  • 使用互斥锁的线程间同步管理。

1.1 初始化pthread_mutex_init()

使用pthread_mutex_init()初始化pthread_mutex_tpthread_mutex_init()第二个参数为nil时,默认 mutex type 为PTHREAD_MUTEX_NORMAL。此时,不提供死锁检测。同一线程尝试多次加锁会导致死锁,从其他线程解锁,或解锁未锁定的锁,都会产生无法预期的结果。

如果 mutex type 为PTHREAD_MUTEX_ERRORCHECK,则会提供错误检测。同一线程尝试多次加锁会返回错误,从其他线程解锁,或解锁未锁定的锁,都会返回错误。PTHREAD_MUTEX_ERRORCHECK类型锁可用于调试。

如果 mutex type 为PTHREAD_MUTEX_RECURSIVE,则锁为递归锁

这里初始化递归锁,方法如下:

    private var ticketMutex: pthread_mutex_t = pthread_mutex_t()
    private var moneyMutex: pthread_mutex_t = pthread_mutex_t()
    
    override init() {
        pthread_mutex_init(&ticketMutex, nil)
        pthread_mutex_init(&moneyMutex, nil)
        
        super.init()
    }

pthread_mutex_init()函数使用指定的 att 属性初始化 mutex。如果 att 为 nil,则使用默认的 mutex attribute。mutex 初始化后为未加锁状态。

尝试初始化已经初始化的 mutex 会产生无法预期的结果。

1.2 加锁pthread_mutex_lock()

调用pthread_mutex_lock()函数给 mutex 对象加锁。如果 mutex 已经加锁,尝试加锁的线程会被堵塞,直到 mutex 解锁。该函数返回加锁的 mutex。

加锁方法如下:

        pthread_mutex_lock(&moneyMutex)

pthread_mutex_trylock()pthread_mutex_lock()用法一样,但当 mutex 已加锁时,pthread_mutex_trylock()会立即返回。

1.3 解锁pthread_mutex_unlock()

pthread_mutex_unlock()解锁 mutex。如果当前有被堵塞的线程,解锁后 mutex 将可用,调度器会决定哪个线程获取 mutex。

        pthread_mutex_unlock(&moneyMutex)

1.4 销毁pthread_mutex_destory()

pthread_mutex_destory()函数销毁 mutex。销毁的 mutex 可以再次使用pthread_mutex_init()初始化,其他方式使用已销毁的 mutex 会产生无法预期的结果。

可以销毁已初始化、未锁定的 mutex,但不能销毁已加锁的 mutex。

        pthread_mutex_destroy(&moneyMutex)

pthread_mutex_init()pthread_mutex_destory()执行成功后,返回0;反之,返回错误码。

完整代码如下:

class NormalMutexDemo: BaseDemo {
    private var ticketMutex: pthread_mutex_t = pthread_mutex_t()
    private var moneyMutex: pthread_mutex_t = pthread_mutex_t()
    
    override init() {
        pthread_mutex_init(&ticketMutex, nil)
        pthread_mutex_init(&moneyMutex, nil)
        
        super.init()
    }
    
    override func saveMoney() {
        pthread_mutex_lock(&moneyMutex)
        
        super.saveMoney()
        
        pthread_mutex_unlock(&moneyMutex)
    }
    
    override func drawMoney() {
        pthread_mutex_lock(&moneyMutex)
        
        super.drawMoney()
        
        pthread_mutex_unlock(&moneyMutex)
    }
    
    override func saleTicket() {
        pthread_mutex_lock(&ticketMutex)
        
        super.saleTicket()
        
        pthread_mutex_unlock(&ticketMutex)
    }
    
    deinit {
        pthread_mutex_destroy(&moneyMutex)
        pthread_mutex_destroy(&ticketMutex)
    }
}

2. NSLock

NSLock使用 POSIX thread 实现锁,是对 pthread_mutex_t 的封装。

不要使用NSLock实现递归锁,在同一线程调用两次 lock 会永久锁定线程。如需实现递归锁,请使用NSRecursiveLock类。

2.1 GNUstep

GNUstep 是 GNU 计划的项目之一,它将 Cocoa 库以自由软件方式重新实现。虽然其不是 Cocoa 的源码,但具有一定参考价值。

GNUstep Downloads下载 GNUstep Base 源码。

2.2 NSLocking协议

NSLocking协议实现了lock()unlock()方法,

  • lock():尝试加锁。会堵塞线程,直到可以获取到锁。
  • unlock():解锁之前获取的锁。

NSRecursiveLockNSConditionNSConditionLock均遵守NSLocking协议。

2.3 初始化NSLock()

使用以下方法初始化锁:

    private var ticketLock = NSLock()

在 GNUstep 的NSLock.m文件中,其初始化代码如下:

/* Use an error-checking lock.  This is marginally slower, but lets us throw
 * exceptions when incorrect locking occurs.
 */
- (id) init
{
    if (nil != (self = [super init])) {
        if (0 != pthread_mutex_init(&_mutex, &attr_reporting)) {
            DESTROY(self);
        }
    }
    return self;
}

可以看到,NSLock底层使用的是pthread_mutex_t

2.4 加锁

NSLocklock()lock(before:)try()三种加锁方式:

2.4.1 lock()

如果已经加锁,lock()会堵塞线程直到可以获取到锁。

        moneyLock.lock()

GNUstep 中实现如下:

#define MLOCK \
- (void) lock\
{\
  int err = pthread_mutex_lock(&_mutex);\
  if (EDEADLK == err)\
    {\
      (*_NSLock_error_handler)(self, _cmd, YES, @"deadlock");\
    }\
  else if (err != 0)\
    {\
      [NSException raise: NSLockException format: @"failed to lock mutex"];\
    }\
}
2.5.2 lock(before:)

lock(before:)在指定时间前尝试加锁,到达指定时间后立即返回。如果加锁成功,返回 true,否则,返回 false。

GNUstep 中实现如下:

- (BOOL) lockBeforeDate: (NSDate*)limit
{
    do {
        int err = pthread_mutex_trylock(&_mutex);
        if (0 == err) {
            CHK(Hold)
            return YES;
        }
        if (EDEADLK == err) {
            (*_NSLock_error_handler)(self, _cmd, NO, @"deadlock");
        }
        sched_yield();
    } while ([limit timeIntervalSinceNow] > 0);
    return NO;
}
2.5.3 try()

try()尝试加锁。如果锁已经锁定,立即返回 false。

GNUstep 中实现如下:

#define MTRYLOCK \
- (BOOL) tryLock\
{\
  int err = pthread_mutex_trylock(&_mutex);\
  if (0 == err) \
    { \
      CHK(Hold) \
      return YES; \
    } \
  else \
    { \
      return NO;\
    } \
}

3. 解锁unlock()

必须在加锁的线程调用unlock()解锁,在其他线程解锁会产生无法预期的错误。解锁未加锁的 lock 属于语法错误。触发此类错误时,NSLock会将错误输出到控制台。

        moneyLock.unlock()

GNUstep 中实现如下:

#define MUNLOCK \
- (void) unlock\
{\
  if (0 != pthread_mutex_unlock(&_mutex))\
    {\
      [NSException raise: NSLockException\
        format: @"failed to unlock mutex"];\
    }\
  CHK(Drop) \
}

Demo名称:Synchronization
源码地址:https://github.com/pro648/BasicDemos-iOS/tree/master/Synchronization

上一篇:线程同步之os_unfair_lock

下一篇:线程同步之递归锁

参考资料:

  1. pthread_mutex_init() pthread_mutex_destroy()
  2. lock and unlock a mutex
  3. NSLock

欢迎更多指正:https://github.com/pro648/tips

本文地址:https://github.com/pro648/tips/blob/master/sources/线程同步之互斥锁.md

相关文章

  • 线程同步与互斥

    Linux--线程编程 多线程编程-互斥锁 线程同步与互斥 互斥锁 信号量 条件变量 互斥锁 互斥锁的基本使用...

  • 网络之美

    线程安全问题----互斥锁和递归锁 互斥锁线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制就是引入互斥锁...

  • 计算机基础-操作系统提升篇

    一,线程,进程同步 为什么需要同步 1.线程同步 以下都是对资源加锁 互斥量(互斥锁)逻辑开始加个锁,结束再解锁,...

  • Linux线程同步

    Linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和信号量。 Linux线程同步-----互斥锁...

  • 第13章 线程安全与锁优化

    第13章线程安全与锁优化 13.2线程安全 13.2.2线程安全的实现方法 1.互斥同步 互斥同步(Mutual ...

  • 并发与多线程

    1线程的同步与互斥。解决线程的同步与互斥 synchronied 和cas乐观锁 还有 lockcas是读取数...

  • 8.1 线程同步

    线程同步(互斥) 简介:同步(互斥)是相对于异步(并发)的概念,线程同步设计到锁的概念; 线程的两大特性:1.可见...

  • iOS重识

    1synchronized(互斥锁) 互斥锁,就是使用了线程同步技术,多条线程按顺序地执行任务 使用场景:多条线程...

  • OpenMP多线程——Parallel for

    多线程——线程同步 数据竞争问题 线程互斥同步——critical 线程互斥同步——atmoic 线程互斥同步——...

  • 并发 :线程间同步、锁、可重入锁及互斥锁

    线程间同步 线程间同步涉及线程互斥锁; 锁(Lock)容易导致死锁,可重入锁(RLock)则不会导致死锁,但每次 ...

网友评论

    本文标题:线程同步之互斥锁

    本文链接:https://www.haomeiwen.com/subject/nmitfltx.html