OC

iOS中的多线程

Posted by sunzhongliang on July 14, 2019

术语:同步、异步、并发、串行

同步异步的主要区别:能不能开启新的线程
同步:在当前线程中执行任务,不具备开启新线程的能力
异步:在新的线程中执行任务,具备开启新线程的能力

并发串行的主要区别:任务的执行方式
并发:多个任务并发(同时)执行
串行:一个任务执行完毕后,在执行下一个任务

GCD的常用函数

GCD中有2个用来执行任务的函数
同步的方式执行任务

// queue:队列
// block:任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

异步的方式执行任务

dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

GCD源码:https://github.com/apple/swift-corelibs-libdispatch

GCD的队列

GCD的队列可以分为2大类型
并发队列(Concurrent Dispatch Queue)
可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
并发功能只有在异步(dispatch_async)函数下才有效

串行队列(Serial Dispatch Queue)
让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

注意点1

注意:使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)

// 以下代码是在主线程执行的,会产生死锁。执行完NSLog(@"执行任务1");后会卡住
NSLog(@"执行任务1");

dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
    NSLog(@"执行任务2");
});

NSLog(@"执行任务3");
// dispatch_sync:马上在当前线程同步执行任务,执行完毕才能往下执行。
// 但是要想执行NSLog(@"执行任务2");需要整个任务执行结束,因为任务2是在队列里。

注意点2

调用performSelector:withObject:本质上是调用objc_msgSend(id obj, SEL sel),但是调用performSelector:withObject:afterDelay:实际上是往RunLoop中添加了一个NSTimer定时器来执行相应代码
以下示例代码中,控制台会输出任务1和任务3,test方法的任务2不会输出

- (void)test
{
    NSLog(@"任务2");
}
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
    NSLog(@"任务1");
    [self performSelector:@selector(test) withObject:nil afterDelay:.0];
    NSLog(@"任务3");
});

子线程中默认没有启动RunLoop,若想要任务2正常执行,则需要手动添加RunLoop

- (void)test
{
    NSLog(@"任务2");
}
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
    NSLog(@"任务1");
    // 这句代码的本质是往Runloop中添加定时器
    [self performSelector:@selector(test) withObject:nil afterDelay:.0];
    NSLog(@"任务3");
    
    // [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    // RunLoop要想启动需要有Source、Timer或者Observer,
    //[performSelector:withObject:afterDelay:]实际上已经拿到RunLoop并且往RunLoop里添加了一个Timer,这里只需要调用RunLoop的run方法
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});

GCD的队列组

需求:异步并发执行任务1、任务2,等任务1、任务2都执行完毕后,再回到主线程执行任务3
示例代码:

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
    NSLog(@"执行任务1");
});
dispatch_group_async(group, queue, ^{
    NSLog(@"执行任务2");
});

dispatch_group_notify(group, queue, ^{
    NSLog(@"所有任务执行完毕");
});

多线程的安全隐患

资源共享问题
同一个资源可能会被多个线程共同读写,很容易出现数据错乱和数据安全问题。
比如卖票系统,假如有10个卖票窗口同时卖1000张票,如果每个窗口在同一时间都在售票,那么最后的剩余票数和售出票数的数量就会不一致。
解决方案就是使用线程同步技术(线程同步:协同步调,按预定的先后次序进行),常见的线程同步技术是线程加锁

iOS中的线程同步方案

  • OSSpinLock
  • os_unfair_lock
  • pthread_mutex
  • dispath_semaphore
  • dispatch_queue(DISPATCH_QUEUE_SERIAL)
  • NSLock
  • NSRecursiveLock
  • NSCondition
  • NSConditionLock
  • @synchronized

OSSpinLock 自旋锁

OSSpinLock叫做”自旋锁”,类似于while循环。等待锁的线程会处于忙等(busy-wait)状态,不会让线程处于休眠状态,一直占用着CPU资源;目前已经不再安全,可能会出现线程优先级反转的问题,在iOS10中苹果将此方法标记为废弃
如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,它会处于 spin lock 的忙等状态从而占用大量 CPU。此时低优先级线程无法与高优先级线程争夺 CPU 时间,从而导致任务迟迟完不成、无法释放 lock

关于优先级反转(Priority Inversion),wikipedia上是这么定义的: 优先级倒置,又称优先级反转、优先级逆转、优先级翻转,是一种不希望发生的任务调度状态。在该种状态下,一个高优先级任务间接被一个低优先级任务所抢先(preemtped),使得两个任务的相对优先级被倒置。 这往往出现在一个高优先级任务等待访问一个被低优先级任务正在使用的临界资源,从而阻塞了高优先级任务;同时,该低优先级任务被一个次高优先级的任务所抢先,从而无法及时地释放该临界资源。这种情况下,该次高优先级任务获得执行权。
使用需要导入头文件 #import <libkern/OSAtomic.h>

OSSpinLock lock = OS_SPINLOCK_INIT; // 初始化
// 这个方法是直接加锁
// OSSpinLockLock(&lock);
// 尝试加锁(如果需要等待就不加锁,返回false;如果不需要等待就加锁,返回true)
bool result = OSSpinLockTry(&lock);
if (result) { // 加锁成功
    // 执行相应代码
    dosomething...
    
    // 解锁
    OSSpinLockUnlock(&lock);
}

os_unfair_lock

os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持
从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等
使用需要导入头文件#import <os/lock.h>

// 初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
// 尝试加锁,加锁失败返回false
os_unfair_lock_trylock(&lock);
// 加锁
os_unfair_lock_lock(&lock);
// 解锁
os_unfair_lock_unlock(&lock);

pthread_mutex

mutex叫做”互斥锁”,等待锁的线程会处于休眠状态。带有pthread开头的一般都是跨平台的
使用需要导入头文件#import <pthread.h>

// 初始化锁的属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); // 锁的类型
// 初始化锁
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);
// 尝试加锁
pthread_mutex_trylock(&mutex);
// 加锁
pthread_mutex_lock(&mutex);
// 解锁
pthread_mutex_unlock(&mutex);
// 销毁相关资源
pthread_mutexattr_destroy(&attr); // 销毁属性
pthread_mutex_destroy(&mutex);  // 销毁锁

mutext锁的attr属性定义值

/*
* Mutex type attributes
*/
#define PTHREAD_MUTEX_NORMAL		0   // 普通锁
#define PTHREAD_MUTEX_ERRORCHECK	1   // 用来检查错误的锁,一般用不上
#define PTHREAD_MUTEX_RECURSIVE		2   // 递归锁
#define PTHREAD_MUTEX_DEFAULT		PTHREAD_MUTEX_NORMAL   // 默认锁,就是普通锁

pthread_mutex - 递归锁

假如有以下递归场景:

// _mutex成员变量已在前方初始化
- (void)test {
    // 这里需要加锁
    pthread_mutex_lock(&_mutex);
    // 递归
    [self test];
    // 解锁
    pthread_mutex_unlock(&_mutex);
}

一旦执行完第一次加锁后,就会发生死锁。因为再次执行test方法时发现锁已经加了,这时候就会等待第一把锁解锁,第一把锁又在等待后续的解锁操作
这时候就要用到pthread_mutex 的递归锁了。递归锁本质上是允许同一个线程对一把锁进行重复加锁

// 初始化锁的属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 改成递归属性即可
// 初始化锁
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);
// ...解锁....销毁资源....

pthread_mutex - 条件锁

需求:方法1调用方法2,执行方法1时加锁,等待方法2执行完成后解锁
示例代码:

// _mutex成员变量已在前方初始化
- (void)test1 {
    // 这里需要加锁
    pthread_mutex_lock(&_mutex);
    // 执行test2方法
    [self test2];
    // 解锁
    pthread_mutex_unlock(&_mutex);
}
- (void)test2 {
    // 这里需要加锁
    pthread_mutex_lock(&_mutex);
    // 执行其他代码
    dosomething...
    // 解锁
    pthread_mutex_unlock(&_mutex);
}

这时候又会发生死锁,因为同一把锁被test1方法加完后,没有解锁进入test2方法,test2会等待test1解锁,test1又等待test2执行完成后才能解锁
使用pthread_mutex 的条件锁解决:

// 初始化锁
pthread_mutex_t mutet;
// NULL代表使用默认属性
pthread_mutex_init(&mutet, NULL);
// 初始化条件
pthread_cond_t condition;
pthread_cond_init(&condition, NULL);
// 等待条件(进入休眠,放开mutex锁;被唤醒后,会再次对mutex加锁)
pthread_cond_wait(&condition, &mutex);
// 激活一个等待该条件的线程
pthread_cond_signal(&condition);
// 激活所有等待该条件的线程
pthread_cond_broadcast(&condition);
// 销毁相关资源
pthread_mutexattr_destroy(&attr);
pthread_mutex_destroy(&mutex);

需求:一个线程向数组添加数据,另外一个线程向数组删除数据,两个线程无法保证执行顺序,但删除数据的线程需保证数组元素大于0
示例代码:

// 属性
@property (assign, nonatomic) pthread_mutex_t mutex; // 锁
@property (assign, nonatomic) pthread_cond_t cond; // 条件
@property (strong, nonatomic) NSMutableArray *data; // 操作的数据

// 方法
- (instancetype)init
{
    if (self = [super init]) {
        // 初始化属性
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);

        // 初始化锁
        pthread_mutex_init(&_mutex, &attr);

        // 销毁属性
        pthread_mutexattr_destroy(&attr);
        
        // 初始化条件
        pthread_cond_init(&_cond, NULL);
        
        self.data = [NSMutableArray array];
    }
    return self;
}
- (void)otherTest
{
    // 线程1 remove
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];

    // 线程2 add
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}

// 生产者-消费者模式
// 线程1 删除数组中的元素
- (void)__remove
{
    pthread_mutex_lock(&_mutex);  // 加锁

    NSLog(@"__remove - begin");
    
    if (self.data.count == 0) {
        // 等待条件,本质上线程相当于睡觉了,睡觉的同时也把锁放开了;
        // 可以理解为线程卡在这一行代码了,等他其他线程将条件放开后,会继续加锁
        pthread_cond_wait(&_cond, &_mutex); 
    }
    
    [self.data removeLastObject];
    NSLog(@"删除了元素");
    
    pthread_mutex_unlock(&_mutex);
}

// 线程2 往数组中添加元素
- (void)__add
{
    pthread_mutex_lock(&_mutex);
    
    sleep(1);
    
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    
    // 通过发送信号方式通知条件,激活另外一个等待该条件的线程。
    // 另外一个等待该条件的线程要想执行任务,需等待该线程把锁放开(unlock)
    pthread_cond_signal(&_cond);

    // 也可通过广播方式,激活所有等待该条件的线程
    // pthread_cond_broadcast(&_cond);
    pthread_mutex_unlock(&_mutex);
}

NSLock、NSRecursiveLock

NSLock是对mutex普通锁的封装,继承自NSObject实现协议

NSRecursiveLock也是对mutex递归锁的封装,同样继承自NSObject实现协议;API跟`NSLock`基本一致

NSLock *lock = [NSLock alloc] init];
// 加锁
[lock lock];
// 解锁
[lock unlock];
// 传入一个时间,在等待(线程休眠)这个时间之前,如果能等待这把锁放开,就给这把锁加锁返回YES,否则返回NO(没有加锁)
[lock BeforeDate:[NSDate distantFuture]];

NSCondition 条件锁

NSCondition是对mutexcond的封装,更加面向对象,方便开发者调用。
需求:一个线程向数组添加数据,另外一个线程向数组删除数据,两个线程无法保证执行顺序,但删除数据的线程需保证数组元素大于0

@interface NSConditionDemo()
@property (strong, nonatomic) NSCondition *condition;
@property (strong, nonatomic) NSMutableArray *data;
@end

@implementation NSConditionDemo

- (instancetype)init
{
    if (self = [super init]) {
        self.condition = [[NSCondition alloc] init];
        self.data = [NSMutableArray array];
    }
    return self;
}

- (void)otherTest
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}

// 生产者-消费者模式

// 线程1
// 删除数组中的元素
- (void)__remove
{
    [self.condition lock];
    NSLog(@"__remove - begin");
    
    if (self.data.count == 0) {
        // 等待
        [self.condition wait];
    }
    
    [self.data removeLastObject];
    NSLog(@"删除了元素");
    [self.condition unlock];
}

// 线程2
// 往数组中添加元素
- (void)__add
{
    [self.condition lock];
    
    sleep(1);
    
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    
    // 信号
    [self.condition signal];
    
    sleep(2);
    
    [self.condition unlock];
}

NSConditionLock 条件锁

NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值
需求:三个线程同时启动,线程一先执行,线程一执行完成后把锁交给线程二,线程二执行完成后把锁交给线程三,如果线程三在进入线程时等待了2秒后一直没拿到锁的控制权,则直接执行。
示例代码:

@interface NSConditionLockDemo()
@property (strong, nonatomic) NSConditionLock *conditionLock;
@end

@implementation NSConditionLockDemo

- (instancetype)init
{
    if (self = [super init]) {
        self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
    }
    return self;
}

- (void)otherTest
{
    // 三个线程同时启动
    NSLog(@"程序启动");
    [[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
}

- (void)__one
{
    // 如果先进入__two方法或者__three,条件锁会休眠等待__one方法先执行
    [self.conditionLock lock];
    
    NSLog(@"__one");
    sleep(1);

    // 解锁,把锁放开给条件2
    [self.conditionLock unlockWithCondition:2];
}

- (void)__two
{
    [self.conditionLock lockWhenCondition:2];
    
    NSLog(@"__two");
    sleep(4);

    // 解锁,把锁放开给条件3
    [self.conditionLock unlockWithCondition:3];
}

- (void)__three
{
    // lockWhenCondition:beforeDate:
    // 在等待(线程休眠)这个beforeDate时间之前,如果能等待这把锁放开,就给这把锁加锁返回YES,否则返回NO(没有加锁),执行后面代码

    // 可判断返回值(加锁成功或失败),执行不同操作
    BOOL result = [self.conditionLock lockWhenCondition:3 beforeDate:[NSDate dateWithTimeIntervalSinceNow:2]];
    
    NSLog(@"__three");
    [self.conditionLock unlock];
}

结果输出(注意输出时间),线程一休眠1秒后把锁放开给线程二执行,线程二休眠4秒钟后把锁放开给线程三执行,线程三一直在等待锁放开,结果方法执行2秒后还没等到,于是就加锁失败继续执行后续代码:

SerialQueue 串行队列

串行队列也是解决线程同步的一个方案

@property (strong, nonatomic) dispatch_queue_t queue1;
self.queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
dispatch_sync(self.moneyQueue, ^{
    // 需注意dosomething需要保证在当前线程内执行

    dosomething...
});

dispatch_semaphore 信号量

semaphore叫做”信号量”,信号量的初始值,可以用来控制线程的最大并发数量,信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
需求:20条线程同时启动,最大只允许5条线程同时执行
示例代码:

@property (strong, nonatomic) dispatch_semaphore_t semaphore;

- (instancetype)init
{
    if (self = [super init]) {
        // 设置最大并发数量
        self.semaphore = dispatch_semaphore_create(5);
        [self test];
    }
    return self;
}
- (void)test
{
    // 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
    // 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    
    sleep(2);
    NSLog(@"test - %@", [NSThread currentThread]);
    
    // 让信号量的值+1
    dispatch_semaphore_signal(self.semaphore);
}

@synchronized

@synchronized是对mutex递归锁的封装,源码查看:objc4中的objc-sync.mm文件,@synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作

// obj是个锁对象,在线程同步操作时,需保证obj是同一个锁对象
@synchoronized(obj) {
    // 执行任务
}

iOS线程同步方案性能比较

性能从高到低排序

  • os_unfair_lock
  • OSSpinLock
  • dispatch_semaphore
  • pthread_mutex
  • dispatch_queue(DISPATCH_QUEUE_SERIAL)
  • NSLock
  • NSCondition
  • pthread_mutex(recursive)
  • NSRecursiveLock
  • NSConditionLock
  • @synchronized

自旋锁、互斥锁比较

什么情况使用自旋锁比较划算?

  • 预计线程等待锁的时间很短
  • 加锁的代码(临界区)经常被调用,但竞争情况很少发生
  • CPU资源不紧张
  • 多核处理器

什么情况使用互斥锁比较划算?

  • 预计线程等待锁的时间较长
  • 单核处理器
  • 临界区有IO操作
  • 临界区代码复杂或者循环量大
  • 临界区竞争非常激烈

iOS中的读写安全

有两种方式可以实现读写安全,一种是通过iOS提供的属性修饰词atomic,另外一种是采用线程控制的方式

  • property的atomic和nonatomic
  • 读写锁pthread_rwlock,dispatch_barrier_async:异步栅栏调用

atomic用于保证属性setter、getter的原子性操作,相当于在getter和setter内部加了线程同步的锁 可以参考源码objc4的objc-accessors.mm
但是它并不能保证使用属性的过程是线程安全的,比如调用NSMutableArray的add和remove方法

需求:同一时间,只能有1个线程进行写的操作,同一时间,允许有多个线程进行读的操作 ,同一时间,不允许既有写的操作,又有读的操作
用传统线程锁比如信号量,在写的时候加锁,读的时候并不加锁;这种情况是不能够保证同一时间只有一个线程读写操作,假如读的方法不加锁,写的方法加锁,看似是实现了多线程同时读取,单线程写入,但是会存在多个线程同时读取和写入的操作,因为读取的方法并没有加锁。

dispatch_barrier_async

这个函数传入的并发队列必须是自己通过dispatch_queue_create创建的
如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果
dispatch_barrier_async实现方式:

#import <pthread.h>

@interface ViewController ()
@property (strong, nonatomic) dispatch_queue_t queue;
@end

@implementation ViewController

// dispatch_barrier_async一般叫做“栅栏函数”,它就好像栅栏一样可以将多个操作分隔开,在它前面追加的操作先执行,在它后面追加的操作后执行
- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 10; i++) {
        dispatch_async(self.queue, ^{
            [self read];
        });
        
        dispatch_async(self.queue, ^{
            [self read];
        });
        
        dispatch_async(self.queue, ^{
            [self read];
        });
        
        // 在它前面的函数先执行,也就会打印3次read,最后再打印write
        dispatch_barrier_async(self.queue, ^{
            [self write];
        });
        // 输出:先3次read,最后1次write
    }
}

- (void)read {
    sleep(1);
    NSLog(@"read");
}

- (void)write
{
    sleep(1);
    NSLog(@"write");
}

@end

pthread_rwlock

等待锁的线程会进入休眠

#import <pthread.h>

@interface ViewController ()
@property (assign, nonatomic) pthread_rwlock_t lock;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 初始化锁
    pthread_rwlock_init(&_lock, NULL);
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            [self read];
        });
        dispatch_async(queue, ^{
            [self write];
        });
    }
}


- (void)read {
    pthread_rwlock_rdlock(&_lock);
    
    sleep(1);
    NSLog(@"%s", __func__);
    
    pthread_rwlock_unlock(&_lock);
}

- (void)write
{
    pthread_rwlock_wrlock(&_lock);
    
    sleep(1);
    NSLog(@"%s", __func__);
    
    pthread_rwlock_unlock(&_lock);
}

- (void)dealloc
{
    pthread_rwlock_destroy(&_lock);
}

@end

线程资源共享

线程之间可以通过静态变量、全局变量、文件等共享资源

线程死锁

形成死锁的四个必要条件是什么?

  1. 互斥条件:线程(进程)对于所分配到的资源具有排它性,即一个资源只能被一个线程(进程)占用,直到被该线程(进程)释放
  2. 请求与保持条件:一个线程(进程)因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:线程(进程)已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
  4. 循环等待条件:当发生死锁时,所等待的线程(进程)必定会形成一个环路(类似于死循环); P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路

如何避免线程死锁

只要破坏产生死锁的四个条件中的其中一个就可以了。

  • 破坏互斥条件
    • 这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
  • 破坏请求与保持条件
    • 一次性申请所有的资源。
  • 破坏不剥夺条件
    • 占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
  • 破坏循环等待条件
    • 靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

互斥锁、递归锁、读写锁和自旋锁区别

互斥锁

一个线程获得资源的使用权后就会将该资源加锁,使用完后会将其解锁。如果在使用过程中,其他线程也要来获取这个资源,那么这个线程将会处于睡眠状态,直到上个线程解锁
简单来说:一个锅,我正在用这个锅做饭,你就没办法去使用,只能乖乖睡觉去,等我做好饭了你才能用这个锅

递归锁

同一个线程可以多次获得该资源,其他线程必须等待该线程释放递归锁才能获得。递归锁常用在递归循环

读写锁

读写锁分为读者写者, 读者可以一同去读,但是和写者互斥.
三个特征:

  • 写者必须互斥,只允许一个写者写,也不能读者写者同时进行
  • 多个读者可以同时进行读
  • 写者优先于读者,一旦有写者,则后续读者必须等待,唤醒时优先考虑写者

自旋锁

当资源被加锁后,其它线程想要再次加锁,此时该线程不会被阻塞睡眠而是陷入循环等待状态(不能再做其它事情),循环检查资源持有者是否已经释放了资源,这样做的好处是减少了线程从睡眠到唤醒的资源消耗,但会一直占用CPU资源。适用于资源的锁被持有的时间短,而不希望在线程的唤醒上花费太多资源的情况

本文首次发布于 孙忠良 Blog, 作者 [@sunzhongliang] , 转载请保留原文链接.