多线程

1.信号量的使用 wait signal  线程同步的方法
semaphore_ = dispatch_semaphore_create(1);
dispatch_semaphore_t semaphore_;
{
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
   
    dispatch_source_t timer = timers_[name];
    if (timer) {
        dispatch_source_cancel(timer);
        [timers_ removeObjectForKey:name];
    }

    dispatch_semaphore_signal(semaphore_);
}

2.iOS的多线程有哪几种?
pthread、NSThread、GCD、NSOperation。后边3种都是基于pthread封装的。

3.如何理解同步、异步、串行、并发
dispatch_sync 同步:在当前线程执行任务,不具备开启新线程的能力;立刻在当前线程执行任务,执行完毕才能往下走。
dispatch_async 异步:在新线程执行任务,具备开启新线程的能力;不要求立刻在当前线程执行任务
串行:一个任务执行完毕后,再执行下一个任务。
并发:多个任务并发(同时)执行。
总结:同步异步研究的能不能开启新的线程。
           串行并发研究的是任务的执行方式。

4.各种队列的执行结果
                      并发队列                 手动创建的串行队列                        主队列
同          没有开启新线程                没有开启新线程                        没有开启新线程
步            串行执行任务                    串行执行任务                          串行执行任务
异            有开启新线程                   有开启新线程                           没有开启新线程
步            并发执行任务                   串行执行任务                           串行执行任务

总结:sync和主队列 一定是在当前线程串行执行任务。
使用sync函数往当前串行队列中添加任务,会卡主当前的串行队列(产生死锁)。
sync+当前串行队列

5.下面代码打印结果是什么?为什么?
13;performSelector:@selector(test) withObject:nil afterDelay:.0本质是往runloop中添加定时器,子线程默认没有启动Runloop.
- (void)test2
{
    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中已经添加了定时器了,runloop就不会退出了。
        //[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    });
}

- (void)test
{
    NSLog(@"2");
}

6.下面2个方法的区别?
performSelector:@selector(test) withObject:nil afterDelay:.0 本质:是向当前runloop添加了一个定时器。
performSelector:@selector(test) withObject:nil = [self test]; 本质是objc_msgsend(self, @selector(test) )

7.如果想看NSArray.m怎么办?
(1)GNUstep不是官方源码,但还是具有一定的参考价值。GNUstep是个组织希望把所有代码都开源。http://www.gnustep.org/resources/downloads.php
(2)打断点一步一步跟进去看汇编。

8.下面代码打印结果是什么?为什么?
打印完1 后崩溃,start后会立刻执行block内的任务,然后在执行test,block和test任务不可能同时执行,当前线程没有runloop执行完任务后会立刻退出,此时向线程中添加test任务会崩溃。
- (void)test
{
    NSLog(@"2");
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        NSLog(@"1");
//        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
//        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }];
    [thread start];
    
    [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}

9.dispatch_group_async使用中dispatch_group_notify可以使用多个吗?可以。
dispatch_group_async使用DISPATCH_QUEUE_CONCURRENT,dispatch_group_notify可以使用dispatch_get_main_queue?可以。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("my_queue", DISPATCH_QUEUE_CONCURRENT);
    
    // 添加异步任务
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务1-%@", [NSThread currentThread]);
        }
    });
    
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务2-%@", [NSThread currentThread]);
        }
    });
    
    // 等前面的任务执行完毕后,会自动执行这个任务
//    dispatch_group_notify(group, queue, ^{
//        dispatch_async(dispatch_get_main_queue(), ^{
//            for (int i = 0; i < 5; i++) {
//                NSLog(@"任务3-%@", [NSThread currentThread]);
//            }
//        });
//    });
    
//    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//        for (int i = 0; i < 5; i++) {
//            NSLog(@"任务3-%@", [NSThread currentThread]);
//        }
//    });
    
    dispatch_group_notify(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务3-%@", [NSThread currentThread]);
        }
    });
    
    dispatch_group_notify(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务4-%@", [NSThread currentThread]);
        }
    });
}

10.static NSString *str = [NSString stringWithFormat:@"123"]为何会编译不过?
static NSString *str = [NSString stringWithFormat:@"123"]; 
static意思是在编译时的值就得确定,而右边的值相当于是一个函数值是在在运行时才能确定,我们可以把这种情况写在dispatch_once里边接口。

11.多线程的安全隐患?
多个线程访问同一块资源,如同一个对象、同一个变量、同一个文件,易引发数据错乱问题。

12.线程阻塞有2种情况?
一种是线程休眠,不占用CPU资源,另外一种是线程忙等,一直占用CPU资源。
忙等的意思是:while(1) ; 这种其实死循环会一直占用CPU自愿的。

13.iOS中的线程同步方案有哪些?
(1)OSSpinLockLock  自旋锁
优点:效率高。主要是相对于需要唤醒线程中runloop的所来说的。
缺点:不安全。它会一直占用着CPU资源,优先级低的线程就无法释放锁。

进程、线程的时间片轮转调度算法:优先级高的并发执行,调度给这个高优先级的进程或线程时间就越多。
 线程优先级:
 thread1:优先级比较高  20ms
 thread2:优先级比较中  15ms
 thread3:优先级比较低  10ms

#import <libkern/OSAtomic.h>
//初始化
self.lock = OS_SPINLOCK_INIT;
// 加锁
OSSpinLockLock(&_lock);
// 解锁
OSSpinLockUnlock(&_lock);

(2)os_unfair_lock -汇编分析是互斥锁
安全:会处于休眠状态,并非忙等。
unfair 不公平的

#import <os/lock.h>
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
os_unfair_lock_lock(&lock);
os_unfair_lock_unlock(&lock);

死锁:执行lock未执行unlock或者unlock地址错误,另外一个线程永远进不来。

(3)pthread_mutex  普通锁PTHREAD_MUTEX_DEFAULT
mutex互斥锁:等待锁的线程会处于休眠状态。

#import <pthread.h>
初始化锁
@property (assign, nonatomic) pthread_mutex_t moneyMutex;
initMutex:(pthread_mutex_t *)mutex{
    // 静态初始化
    //pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
      //初始化属性
      pthread_mutexattr_t attr;
      pthread_mutexattr_init(&attr);
      pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
      // 初始化锁
      pthread_mutex_init(mutex, &attr);//pthread_mutex_init(mutex, NULL);等价于DEFAULT属性
      // 销毁属性
      pthread_mutexattr_destroy(&attr);
}

使用锁
    pthread_mutex_lock(&_moneyMutex);
    pthread_mutex_unlock(&_moneyMutex);

销毁锁
dealloc {
      pthread_mutex_destroy(&_moneyMutex);
}

(4)pthread_mutex 递归锁:允许同一个线程对一把锁进行重复加锁 PTHREAD_MUTEX_RECURSIVE
- (void)__initMutex:(pthread_mutex_t *)mutex {
    // 初始化属性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    // 初始化锁
    pthread_mutex_init(mutex, &attr);
    // 销毁属性
    pthread_mutexattr_destroy(&attr);
}

如果一个线程在执行,其他线程就会等待。
- (void)otherTest {
    pthread_mutex_lock(&_mutex);
    NSLog(@"%s", __func__);
    static int count = 0;
    if (count < 10) {
        count++;
        [self otherTest];
    }
    pthread_mutex_unlock(&_mutex);
}

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

(5)pthread_mutex 条件锁
使用场景:用于线程之间的依赖,线程A需要等待线程B做完再回到线程A做事情。

#import <pthread.h>
@interface MutexDemo3()
@property (assign, nonatomic) pthread_mutex_t mutex;
@property (assign, nonatomic) pthread_cond_t cond;
@property (strong, nonatomic) NSMutableArray *data;
@end

@implementation MutexDemo3

- (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
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    [[[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) {
        // 等待条件(进入休眠即阻塞停在这里,放开mutex;被唤醒(执行signal)后,如果没有加锁,会再次对mutex加锁,如果加锁了就等待,被放开后会再加锁)
        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(@"添加了元素");
    
    // 激活一个等待该条件的线程
    pthread_cond_signal(&_cond); //注意这句代码写在这里和pthread_mutex_unlock后的区别?写在这里的话,上面wait后的代码会等pthread_mutex_unlock执行完才执行;写在pthread_mutex_unlock后,上面wait后的代码会立刻执行。
    // 激活所有等待该条件的线程
//    pthread_cond_broadcast(&_cond);
    pthread_mutex_unlock(&_mutex);
}

- (void)dealloc
{
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_cond);
}

(6)NSLock
NSLock是对mutex普通锁的封装,没区别仅封装而已。
GNUStep可以看到源码。

(7)NSRecursivelock
NSRecursivelock对mutex递归锁的封装

(8)NSCondition 
NSCondition是对mutex和cond两个的封装

@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];
    
    // 广播
//    [self.condition broadcast];
    [self.condition unlock];
    
}
@end

(9)NSConditionLock
NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值。
使用场景:多线程下的任务依赖。与cond区别是:A->B->A cond 在A中等B执行还会回到A,即会不会回到A中的区别。

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

@implementation NSConditionLockDemo

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

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

- (void)__one
{
    [self.conditionLock lock]; //不管默认值是几都会进来
    
    NSLog(@"__one");
    sleep(1);
    
    [self.conditionLock unlockWithCondition:2];
}

- (void)__two
{
    [self.conditionLock lockWhenCondition:2];
    
    NSLog(@"__two");
    sleep(1);
    
    [self.conditionLock unlockWithCondition:3];
}

- (void)__three
{
    [self.conditionLock lockWhenCondition:3];
    
    NSLog(@"__three");
    
    [self.conditionLock unlock];
}
@end

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

@interface SerialQueueDemo()
@property (strong, nonatomic) dispatch_queue_t ticketQueue;
@property (strong, nonatomic) dispatch_queue_t moneyQueue;
@end

@implementation SerialQueueDemo
- (instancetype)init {
    if (self = [super init]) {
        self.ticketQueue = dispatch_queue_create("ticketQueue", DISPATCH_QUEUE_SERIAL);
        self.moneyQueue = dispatch_queue_create("moneyQueue", DISPATCH_QUEUE_SERIAL);
    }
    return self;
}

- (void)__drawMoney {
    dispatch_sync(self.moneyQueue, ^{
        [super __drawMoney];
    });
}

- (void)__saveMoney {
    dispatch_sync(self.moneyQueue, ^{
        [super __saveMoney];
    });
}

- (void)__saleTicket {
    dispatch_sync(self.ticketQueue, ^{
        [super __saleTicket];
    });
}
@end

(11)semaphore -1 +1
设置线程最大并发量,信号量>1即可;线程同步=1,信号量=1即可。

@interface SemaphoreDemo()
@property (strong, nonatomic) dispatch_semaphore_t semaphore;
@property (strong, nonatomic) dispatch_semaphore_t ticketSemaphore;
@property (strong, nonatomic) dispatch_semaphore_t moneySemaphore;
@end

@implementation SemaphoreDemo

- (instancetype)init {
    if (self = [super init]) {
        self.semaphore = dispatch_semaphore_create(5);
        self.ticketSemaphore = dispatch_semaphore_create(1);
        self.moneySemaphore = dispatch_semaphore_create(1);
    }
    return self;
}

- (void)__drawMoney {
    dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);
    [super __drawMoney];
    dispatch_semaphore_signal(self.moneySemaphore);
}

- (void)__saveMoney {
    dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);
    [super __saveMoney];
    dispatch_semaphore_signal(self.moneySemaphore);
}

- (void)__saleTicket {
    dispatch_semaphore_wait(self.ticketSemaphore, DISPATCH_TIME_FOREVER);
    [super __saleTicket];
    dispatch_semaphore_signal(self.ticketSemaphore);
}

- (void)otherTest {
    for (int i = 0; i < 20; i++) {
        [[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
    }
}

// 线程10、7、6、9、8
- (void)test {
    // 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
    // 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER); //-1
    sleep(2);
    NSLog(@"test - %@", [NSThread currentThread]);
    // 让信号量的值+1
    dispatch_semaphore_signal(self.semaphore); //+1
}
@end

(12)@synchronized  递归锁
源码查看:objc4中的objc-sync.mm文件
@synchronized是对mutex递归锁的封装,所以可以嵌套使用。
@synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作
缺点:性能差,封装了哈希表,又将mutex封装成了C++的类

@implementation SynchronizedDemo
//如果synchronized后都使用[self class],所有对象调用都用了一把锁,
//如果synchronized后都使用self,不同对象调用都用了一把锁,


- (void)__drawMoney
{
    @synchronized([self class]) {
        [super __drawMoney];
    }
}

- (void)__saveMoney
{
    @synchronized([self class]) { // objc_sync_enter
        [super __saveMoney];
    } // objc_sync_exit
}

- (void)__saleTicket
{
    static NSObject *lock;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        lock = [[NSObject alloc] init];
    });
    
    @synchronized(lock) {
        [super __saleTicket];
    }
}

- (void)otherTest
{
    @synchronized([self class]) {
        NSLog(@"123");
        [self otherTest];
    }
}
@end

14.锁的性能对比:C > OC 
dispatch_semaphore、pthread_mutex使用较多

性能从高到低排序
os_unfair_lock
OSSpinLock
dispatch_semaphore
pthread_mutex
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSCondition
pthread_mutex(recursive)
NSRecursiveLock
NSConditionLock
@synchronized

15.保住线程的命为什么要用runloop,用强指针不就好了嘛?
用强指针虽然可以是线程在内存中,但是还是它还是不能用,因为还需要让线程保持激活状态。

16.自旋锁、互斥锁使用场景有哪些?
iOS自旋锁已经基本不能使用,基本都是互斥锁了。

什么情况使用自旋锁比较划算?
(1)预计线程等待锁的时间很短;
(2)加锁的代码(临界区)经常被调用,但竞争情况很少发生;
(3)CPU资源不紧张;
(4)多核处理器.

什么情况使用互斥锁比较划算?
(1)预计线程等待锁的时间较长;
(2)临界区有IO操作;
(3)临界区代码复杂或者循环量大;
(4)临界区竞争非常激烈;
(5)单核处理器。

17.atomic 一般mac使用多
tomic用于保证属性setter、getter的原子性操作,相当于在getter和setter内部加了线程同步的锁

atomic和monatomic区别?
-(void)setIcon1:(UIImage *)icon1
{           @synchronized (self) {//同步代码块 
                      if(_icon1 != icon1) {
                              [_icon1 release];
                              _icon1 = [icon1 retain];
                       }
            }
}
修饰基本数据类型没区别;
atomic修饰oc对象多线程下不会崩溃,但结果无法预知。
monatomic修饰oc对象多线程下会崩溃。

atomic原子性并不能保证多线程安全,只是能保证数据的完整性
这个完整性体现在:使用者总能取到完整的值

无法预知的原因:虽然atomic保证了number属性线程安全了,但是并不能保证temp变量的线程安全,又因为是多线程的,所以有可能同时执行多次 int temp = _number+1;才执行一次 _number = temp;导致结果每次都不同,而且结果不可预知。

崩溃原因:因为是多线程,且没有加锁保护,所以在一个线程走到[_name release]后,可能在另一个线程又一次去释放,这时候造成崩溃。

18.实现多读单写的方案有哪些?
思考如何实现以下场景
同一时间,只能有1个线程进行写的操作
同一时间,允许有多个线程进行读的操作
同一时间,不允许既有写的操作,又有读的操作

上面的场景就是典型的“多读单写”,经常用于文件等数据的读写操作,iOS中的实现方案有
(1)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


(2)dispatch_barrier_async:异步栅栏调用

这个函数传入的并发队列必须是自己通过dispatch_queue_cretate创建的,
如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果
dispatch_barrier_async(串行, ^{
            [self write];
});
等于
dispatch_async(串行, ^{
            [self write];
});

dispatch_barrier_async(全局并发, ^{
            [self write];
});
等于
dispatch_async(全局并发, ^{
            [self write];
});

#import <pthread.h>

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

@implementation ViewController

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


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

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

@end



©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容