iOS原理(八)----多线程

iOS原理(八)----多线程

基本概念

iOS常见的多线程方案有以下四种:

  • pthread:是一套通用的多线程C语言API,适用于Unix\Linux\Windows等系统,可以跨平台可移植,使用难度大,程序员管理其生命周期.
  • NSThread:面向对象,简单易用,可直接操作线程对象的OC语言的API,程序员管理其生命周期.
  • GCD:旨在替代NSThread等线程技术,充分利用设备的多核的C语言API,自动管理其生命周期.
  • NSOperation:基于GCD(底层是GCD),比GCD多了一些更简单实用的功能,使用更加面向对象的OC语言的API,自动管理其生命周期.

同步:在当前线程中执行任务,不具备开启新线程的能力.

异步:在新的线程中执行任务,具备开启新线程的能力.

并发:多个任务并发(同时)执行.

串行:一个任务执行完毕后,再执行下一个任务.

|并发队列|串行队列|主队列
:--:|:--:|:--:|:--:
同步|没有开启新线程,串行执行任务|没有开启新线程,串行执行任务|没有开启新线程,串行执行任务
异步|有开启新线程,并发执行任务|有开启新线程,串行执行任务|没有开启新线程,串行执行任务

GCD队列组的使用

当以需要几个异步并发任务执行完后在统一执行某个操作时可以使用GCD队列组.

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("test1", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"111");
    });
    dispatch_async(queue, ^{
        NSLog(@"222");
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"333");
    });

线程同步

当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题,使用线程同步技术,保证多线程访问同一资源安全问题.比如存取钱的问题:钱总额为1000,分10次存钱和取钱,每次为50,按理总后还是剩下1000.

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.money = 1000;
    
    [self saveMoney];
    [self withdrawMoney];
}

- (void)saveMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            int old = self.money;
            sleep(0.3);
            old += 50;
            self.money = old;
            NSLog(@"存钱50元,总额为%d",self.money);
        });
    }
}

- (void)withdrawMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            int old = self.money;
            sleep(0.3);
            old -= 50;
            self.money = old;
            NSLog(@"取钱50元,总额为%d",self.money);
        });
    }
}

最后打印结果如下:结果为900,可见有线程安全问题.

Snip20181117_1.png

解决方案有以下几种:

1.OSSpinLock

OSSpinLock叫做”自旋锁”,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源.

#import "ViewController.h"
#import 

@interface ViewController ()

@property (assign, nonatomic) int money;

@property (assign, nonatomic) OSSpinLock lock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.lock = OS_SPINLOCK_INIT;
    self.money = 1000;
    
    [self saveMoney];
    [self withdrawMoney];
}

- (void)saveMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            OSSpinLockLock(&_lock);
            int old = self.money;
            sleep(0.3);
            old += 50;
            self.money = old;
            NSLog(@"存钱50元,总额为%d",self.money);
            OSSpinLockUnlock(&_lock);
        });
    }
}

- (void)withdrawMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            OSSpinLockLock(&_lock);
            int old = self.money;
            sleep(0.3);
            old -= 50;
            self.money = old;
            NSLog(@"取钱50元,总额为%d",self.money);
             OSSpinLockUnlock(&_lock);
        });
    }
}

@end

打印如下,结果一致:

Snip20181117_2.png

目前, OSSpinLock已经不安全,如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁,可能会出现优先级反转问题.

2.os_unfair_lock

os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持,从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等.

#import "ViewController.h"
#import 

@interface ViewController ()

@property (assign, nonatomic) int money;

@property (assign, nonatomic) os_unfair_lock lock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.lock = OS_UNFAIR_LOCK_INIT;
    self.money = 1000;
    
    [self saveMoney];
    [self withdrawMoney];
}

- (void)saveMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            os_unfair_lock_lock(&self->_lock);
            int old = self.money;
            sleep(0.3);
            old += 50;
            self.money = old;
            NSLog(@"存钱50元,总额为%d",self.money);
            os_unfair_lock_unlock(&self->_lock);
        });
    }
}

- (void)withdrawMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
             os_unfair_lock_lock(&self->_lock);
            int old = self.money;
            sleep(0.3);
            old -= 50;
            self.money = old;
            NSLog(@"取钱50元,总额为%d",self.money);
            os_unfair_lock_unlock(&self->_lock);
        });
    }
}

@end

打印结果如下:

Snip20181117_3.png

3.pthread_mutex

mutex叫做”互斥锁”,等待锁的线程会处于休眠状态.

#import "ViewController.h"
#import 

@interface ViewController ()

@property (assign, nonatomic) int money;

@property (assign, nonatomic) pthread_mutex_t lock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    pthread_mutex_init(&_lock,NULL);
    self.money = 1000;
    
    [self saveMoney];
    [self withdrawMoney];
}

- (void)saveMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            pthread_mutex_lock(&self->_lock);
            int old = self.money;
            sleep(0.3);
            old += 50;
            self.money = old;
            NSLog(@"存钱50元,总额为%d",self.money);
            pthread_mutex_unlock(&self->_lock);
        });
    }
}

- (void)withdrawMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            pthread_mutex_lock(&self->_lock);
            int old = self.money;
            sleep(0.3);
            old -= 50;
            self.money = old;
            NSLog(@"取钱50元,总额为%d",self.money);
            pthread_mutex_unlock(&self->_lock);
        });
    }
}

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

@end

打印结果如下:

Snip20181117_4.png

4.pthread_mutex – 递归锁

当函数中存在递归调用时,又希望每次调用都是线程安全的,这是可以使用pthread_mutex – 递归锁.

#import "ViewController.h"
#import 

@interface ViewController ()

@property (assign, nonatomic) int money;

@property (assign, nonatomic) pthread_mutex_t lock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    
    pthread_mutex_init(&_lock,&attr);
    
    pthread_mutexattr_destroy(&attr);

}

- (void)test {
    pthread_mutex_lock(&_lock);
    static int num = 10;
    while (num > 0) {
        num--;
    }
    [self test];
    pthread_mutex_unlock(&_lock);
}

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

@end

4.pthread_mutex – 条件锁

在进行线程同步时,需要某些条件成立时,才执行某些代码时,这是可以使用条件锁:pthread_mutex – 条件锁.在前面的条件中假如必须钱不小于1000,才能取钱.

#import "ViewController.h"
#import 

@interface ViewController ()

@property (assign, nonatomic) int money;

@property (assign, nonatomic) pthread_cond_t con;

@property (assign, nonatomic) pthread_mutex_t lock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    pthread_cond_init(&_con, NULL);
    pthread_mutex_init(&_lock, NULL);
    
    self.money = 1000;
    
    [self saveMoney];
    [self withdrawMoney];

}


- (void)saveMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            pthread_mutex_lock(&self->_lock);
            int old = self.money;
            sleep(0.3);
            old += 50;
            self.money = old;
            NSLog(@"存钱50元,总额为%d",self.money);
            pthread_mutex_unlock(&self->_lock);
            pthread_cond_signal(&self->_con);
        });
    }
}

- (void)withdrawMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            pthread_mutex_lock(&self->_lock);
            int old = self.money;
            if (old < 1000) {
                pthread_cond_wait(&self->_con, &self->_lock);
            }
            sleep(0.3);
            old -= 50;
            self.money = old;
            NSLog(@"取钱50元,总额为%d",self.money);
            pthread_mutex_unlock(&self->_lock);
        });
    }
}

- (void)dealloc {
    pthread_mutex_destroy(&_lock);
    pthread_cond_destroy(&_con);
}

@end
Snip20181119_5.png

5.NSLock

NSLock是对mutex普通锁的封装.

#import "ViewController.h"

@interface ViewController ()

@property (assign, nonatomic) int money;


@property (strong, nonatomic) NSLock *lock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.lock = [[NSLock alloc] init];
    
    self.money = 1000;
    
    [self saveMoney];
    [self withdrawMoney];

}


- (void)saveMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self.lock lock];
            int old = self.money;
            sleep(0.3);
            old += 50;
            self.money = old;
            NSLog(@"存钱50元,总额为%d",self.money);
            [self.lock  unlock];
        });
    }
}

- (void)withdrawMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self.lock lock];
            int old = self.money;
            sleep(0.3);
            old -= 50;
            self.money = old;
            NSLog(@"取钱50元,总额为%d",self.money);
            [self.lock unlock];
        });
    }
}


@end

打印如下:

Snip20181119_6.png

6.NSRecursiveLock

NSRecursiveLock也是对mutex递归锁的封装,API跟NSLock基本一致.

#import "ViewController.h"

@interface ViewController ()

@property (strong, nonatomic) NSRecursiveLock *lock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.lock = [[NSRecursiveLock alloc] init];
}

- (void)test {
    [self.lock lock];
    static int num = 10;
    while (num > 0) {
        num--;
    }
    [self test];
    [self.lock unlock];
}

@end

7.NSCondition

NSCondition是对mutex和cond的封装.

#import "ViewController.h"

@interface ViewController ()

@property (assign, nonatomic) int money;


@property (strong, nonatomic) NSCondition *lock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.lock = [[NSCondition alloc] init];

    self.money = 1000;

    [self saveMoney];
    [self withdrawMoney];

}


- (void)saveMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self.lock lock];
            int old = self.money;
            sleep(0.3);
            old += 50;
            self.money = old;
            NSLog(@"存钱50元,总额为%d",self.money);
            [self.lock  unlock];
            [self.lock signal];
        });
    }
}

- (void)withdrawMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self.lock lock];
            int old = self.money;
            if (old < 100) {
                [self.lock wait];
            }
            sleep(0.3);
            old -= 50;
            self.money = old;
            NSLog(@"取钱50元,总额为%d",self.money);
            [self.lock unlock];
        });
    }
}


@end
Snip20181119_7.png

8.NSConditionLock

NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值.假设我们要先存钱,才能取钱.

#import "ViewController.h"

@interface ViewController ()

@property (assign, nonatomic) int money;


@property (strong, nonatomic) NSConditionLock *lock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.lock = [[NSConditionLock alloc] init];

    self.money = 1000;

    [self saveMoney];
    [self withdrawMoney];

}


- (void)saveMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self.lock lock];
            int old = self.money;
            sleep(0.3);
            old += 50;
            self.money = old;
            NSLog(@"存钱50元,总额为%d",self.money);
            [self.lock  unlockWithCondition:1];
        });
    }
}

- (void)withdrawMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self.lock lockWhenCondition:1];
            int old = self.money;
            sleep(0.3);
            old -= 50;
            self.money = old;
            NSLog(@"取钱50元,总额为%d",self.money);
            [self.lock unlock];
        });
    }
}


@end

打印如下:

Snip20181119_8.png

9.dispatch_semaphore

semaphore叫做”信号量”,信号量的初始值,可以用来控制线程并发访问的最大数量,信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步.

#import "ViewController.h"

@interface ViewController ()

@property (assign, nonatomic) int money;

@property (strong, nonatomic) dispatch_semaphore_t lock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.lock = dispatch_semaphore_create(1);

    self.money = 1000;

    [self saveMoney];
    [self withdrawMoney];

}


- (void)saveMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            dispatch_semaphore_wait(self.lock, DISPATCH_TIME_FOREVER);
            int old = self.money;
            sleep(0.3);
            old += 50;
            self.money = old;
            NSLog(@"存钱50元,总额为%d",self.money);
            dispatch_semaphore_signal(self.lock);
        });
    }
}

- (void)withdrawMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
             dispatch_semaphore_wait(self.lock, DISPATCH_TIME_FOREVER);
            int old = self.money;
            sleep(0.3);
            old -= 50;
            self.money = old;
            NSLog(@"取钱50元,总额为%d",self.money);
            dispatch_semaphore_signal(self.lock);
        });
    }
}


@end
Snip20181119_9.png

10.dispatch_queue

直接使用GCD的串行队列,也是可以实现线程同步的.

#import "ViewController.h"

@interface ViewController ()

@property (assign, nonatomic) int money;

@property (strong, nonatomic) dispatch_queue_t queue;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);

    self.money = 1000;

    [self saveMoney];
    [self withdrawMoney];

}


- (void)saveMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_sync(self.queue, ^{
            int old = self.money;
            sleep(0.3);
            old += 50;
            self.money = old;
            NSLog(@"存钱50元,总额为%d",self.money);
        });
    }
}

- (void)withdrawMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_sync(self.queue, ^{
            int old = self.money;
            sleep(0.3);
            old -= 50;
            self.money = old;
            NSLog(@"取钱50元,总额为%d",self.money);
        });
    }
}


@end
Snip20181119_10.png

11.@synchronized

@synchronized是对mutex递归锁的封装, @synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作.

#import "ViewController.h"

@interface ViewController ()

@property (assign, nonatomic) int money;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.money = 1000;

    [self saveMoney];
    [self withdrawMoney];

}


- (void)saveMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            @synchronized (self) {
                int old = self.money;
                sleep(0.3);
                old += 50;
                self.money = old;
                NSLog(@"存钱50元,总额为%d",self.money);
            }
        });
    }
}

- (void)withdrawMoney {
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            @synchronized (self) {
                int old = self.money;
                sleep(0.3);
                old -= 50;
                self.money = old;
                NSLog(@"取钱50元,总额为%d",self.money);
            }
        });
    }
}


@end
Snip20181119_12.png

以上所有性能由高到低为: os_unfair_lock, OSSpinLock, dispatch_semaphore, pthread_mutex, dispatch_queue(DISPATCH_QUEUE_SERIAL), NSLock, NSCondition, pthread_mutex(recursive), NSRecursiveLock, NSConditionLock, @synchronized.

自旋锁、互斥锁比较

使用自旋锁比较划算的情况:

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

使用互斥锁比较划算的情况:

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

atomic

atomic用于保证属性setter、getter的原子性操作,相当于在getter和setter内部加了线程同步的锁,它并不能保证使用属性的过程是线程安全的.

iOS中的读写安全方案

如果同一时间,只能有1个线程进行写的操作,允许有多个线程进行读的操作,不允许既有写的操作,又有读的操作.上面的场景就是典型的“多读单写”,经常用于文件等数据的读写操作,iOS中的实现方案有:

  • pthread_rwlock:读写锁
#import "ViewController.h"
#import 

@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 read];
        });
        
        dispatch_async(queue, ^{
            [self write];
        });
    }
}

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

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

@end

import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

  • (void)viewDidLoad {
    [super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_CONCURRENT);

for (int i = 0; i < 10; i ++) {
    dispatch_async(queue, ^{
        [self read];
    });
    
    dispatch_async(queue, ^{
        [self read];
    });
    
    dispatch_barrier_async(queue, ^{
        [self write];
    });
}

}

  • (void)read {
    sleep(1);
    NSLog(@"%s",func);
    }

  • (void)write {
    sleep(1);
    NSLog(@"%s",func);
    }

@end


[图片上传失败...(image-88006d-1545884238736)]

你可能感兴趣的:(iOS原理(八)----多线程)