多线程

作者: 万万万万万万一 | 来源:发表于2023-07-13 20:46 被阅读0次

进程、线程

进程

当一个程序进入内存运行,即变成一个进程。进程是处于运行过程中的程序,并且具有一定独立功能。

线程

线程是进程中的一个执行单元,负责当前进程中程序的之心,一个进程至少有一个线程。一个进程中可以有多个线程。

单线程程序:若有多个任务只能一次执行
多线程程序:若有多个任务,可以同时执行

对于CPU单一个核心而言,某个时刻只能执行一个线程,而CPU在多个线程之间切换的速度相对我们的感觉要快,看上去就是在同一时刻运行。
多线程并不能提高程序的运行速度,但能提高运行效率

任务

任务:执行操作的意思,就是要在线程中执行的代码

同步执行(sync)

同步添加任务到队列中,队列在任务结束之前会一直等待,直到任务完成之后再继续执行
只能在当前线程中执行任务,不具备开启新线程的能力

异步执行(async)

异步添加任务到队列中,队列不会等待,可以继续执行其他任务。
可以在新的线程中执行任务,具备开启线程的能力,但不一定开启新线程。
队列

队列(dispatch queue)

执行任务的等待队列,即用来存在任务的队列。队列是一种特殊的线性表,采用FIFO(first in first out)的原则。新的任务总是被插到队列的末尾,读取任务总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。

串行队列(serial dispatch queue)

只开启一个线程,每次只能执行一个任务,一个任务执行完毕后才能执行下一个任务。

并发队列(concurrent dispatch queue)

可以让多个任务并发(同时)执行,可以开启多个线程,并同时执行任务。并行队列的并发功能只能在异步下才有效。

主队列

是一个特殊的串行队列,添加到主队列的任务只能在主线程中执行

tttt.png

iOS多线程方案

ggggg.png

队列 组Group 的使用

异步并发执行任务1、任务2,等任务1、任务2都执行完毕后,再回到主线程执行任务3

    // 创建调度组
    dispatch_group_t group = dispatch_group_create();
    //创建并发队列
    dispatch_queue_t que = dispatch_queue_create("qye", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_async(group, que, ^{
        NSLog(@"任务1");
    });
    
    dispatch_group_async(group, que, ^{
        NSLog(@"任务2");
    });
    
    dispatch_group_notify(group, que, ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"任务3");
        });
    });

栅栏函数 dispatch_barrier_async

这个函数传入的并发队列必须是自己通过dispatch_queue_cretate创建的

如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果

栅栏函数是GCD提供的用于阻塞分割任务的一组函数。就像其定义一样,其主要作用就是在队列中设置栅栏,来人为干预队列中任务的执行顺序.常用的栅栏函数有两个##dispatch_barrier_async和dispatch_barrier_sync.

dispatch_barrier_async:用于提交异步执行栅栏函数块任务,并立即返回.所以该函数不会阻塞线程,只会阻塞任务执行。栅栏函数提交之后并不会立即执行,而是会直接返回。等待在栅栏函数之前提交的任务都执行完成之后,栅栏函数任务会自动执行,在栅栏函数之后提交的任务必须在栅栏函数执行完成之后才会执行.
dispatch_barrier_sync:用于提交同步执行栅栏函数块任务,并不会立即返回,需要等待栅栏函数提交的任务执行完毕之后才会返回。与dispatch_barrier_async不同,由于该函数需要同步执行,在该栅栏函数任务执行完成之前,函数不会返回,所以此时后边的任务都会被阻塞.

栅栏函数可以干嘛?

假设我们有三个任务task 001-003,要求task 001 task 002顺序不定,task 003 需要在task 001 和 task 002执行完成之后才可以执行,使用dispatch_barrier_async

    dispatch_queue_t queue = dispatch_queue_create("com.queueDemo", 0);
    dispatch_async(queue, ^{
        NSLog(@"task 001 --- %@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"task 002 --- %@", [NSThread currentThread]);
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"task 003 --- %@", [NSThread currentThread]);
    });
    NSLog(@"task 004 --- %@", [NSThread currentThread]);
 
输出结果为:
task 004 --- <NSThread: 0x282c34cc0>{number = 1, name = main}
task 001 --- <NSThread: 0x282c6c540>{number = 3, name = (null)}
task 002 --- <NSThread: 0x282c6c540>{number = 3, name = (null)}
task 003 --- <NSThread: 0x282c6c540>{number = 3, name = (null)}

如果使用dispatch_barrier_sync

    dispatch_queue_t queue = dispatch_queue_create("com.queueDemo", 0);
    dispatch_async(queue, ^{
        NSLog(@"task 001 --- %@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"task 002 --- %@", [NSThread currentThread]);
    });
    dispatch_barrier_sync(queue, ^{
        NSLog(@"task 003 --- %@", [NSThread currentThread]);
    });
    NSLog(@"task 004 --- %@", [NSThread currentThread]);
 
输出结果:(task 001 task 002随机, task 003在任务task 001 task 002执行完成之后才能执行)
task 001 --- <NSThread: 0x280a132c0>{number = 3, name = (null)}
task 002 --- <NSThread: 0x280a132c0>{number = 3, name = (null)}
task 003 --- <NSThread: 0x280a46e80>{number = 1, name = main}
task 004 --- <NSThread: 0x280a46e80>{number = 1, name = main}


使用栅栏函数需要注意什么?

跟所有的串行队列一样,向串行队列中添加任务,要防止在串行队类中堵塞线程导致的死锁.例如

    dispatch_queue_t queue = dispatch_queue_create("com.supportHongKongPolice", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        dispatch_async(queue, ^{
            NSLog(@"task 001");
        });
        dispatch_barrier_sync(queue, ^{
            NSLog(@"task 002");
        });
        NSLog(@"task 003");
    });


信号量dispatch_semaphore

semaphore叫做”信号量”

信号量的初始值,可以用来控制线程并发访问的最大数量

信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步

   // 信号量初始值
    int vaue = 1;
    //创建信号量
    dispatch_semaphore_t sema = dispatch_semaphore_create(vaue);
    
    /*
     这个函数的作用是这样的,如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;
       如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t,
       不能直接传入整形或float型数),如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,
       且该函数(即dispatch_semaphore_wait)所处线程获得了信号量,那么就继续向下执行并将信号量减1。
       如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。
     */
    
    //dispatch_semaphore_wait 等待信号量
//    long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
//    这个函数会使传入的信号量dsema的值减1,如果传入信号量的值等于0,函数将持续等待不返回
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    
    /*
     dispatch_semaphore_signal 发送信号量
     long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
     这个函数会使传入的信号量dsema的值加1
     */
    dispatch_semaphore_signal(sema);

多线程安全

1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,同一个对象、同一个变量、同一个文件

当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题

解决方案:使用线程同步技术,如:加锁

OSSpinLock

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

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

需要导入头文件#import <libkern/OSAtomic.h>

// 初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
// 尝试加锁,需要等待就不加锁,直接返回FALSE,不需要等待就加锁,返回true
bool res = OSSpinLockTry(&lock);
// 加锁
OSSpinLockLock(&lock);
// 解锁
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;
// 尝试加锁
os_unfair_lock_trylock(&lock)
// 加锁
os_unfair_lock_lock(&lock);
// 解锁
os_unfair_lock_unlock(&lock);

pthread_mutex

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

需要导入头文件#import <pthread.h>

pthread_mutex 锁类型

1.#define PTHREAD_MUTEX_NORMAL 0
2.#define PTHREAD_MUTEX_ERRORCHECK 1
3.#define PTHREAD_MUTEX_RECURSIVE 2 递归锁
4.#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL

普通锁

// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);

// 初始化锁
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);

递归锁

// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);

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

条件锁。等待进入休眠,放开mutex锁;被唤醒后,会再次对mutex加锁

// 初始化锁
pthread_mutex_t mutex;
// NULL代表使用默认属性
pthread_mutex_init(&mutex, NULL);

// 初始化条件
pthread_cond_t cond;
pthread_cond_init(&cond, NULL);

// 等待条件(进入休眠,放开mutex锁;被唤醒后,会再次对mutex加锁)
pthread_cond_wait(&cond, &mutex);

// 激活一个等待该条件的线程
pthread_cond_signal(&cond);

// 激活所有等待该条件的线程
pthread_cond_broadcast(&cond);

// 销毁资源
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);

NSLock

NSLock是对mutex普通锁的封装,面向对象

@protocol NSLocking

- (void)lock;
- (void)unlock;

@end

@interface NSLock : NSObject <NSLocking> {
@private
    void *_priv;
}

- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

NSRecursiveLock

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

NSCondition

NSCondition 是对 mutex条件锁的封装

@interface NSCondition : NSObject <NSLocking> {
@private
    void *_priv;
}

- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

NSConditionLock

NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值

@interface NSConditionLock : NSObject <NSLocking> {
@private
    void *_priv;
}

- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;

@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

@synchronized

@synchronized是对mutex递归锁的封装

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

@synchronized(obj) { // objc_sync_enter
        
}

线程同步方案对比

性能方面,性能从高到低排序

os_unfair_lock
OSSpinLock
dispatch_semaphore
pthread_mutex
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSCondition
pthread_mutex(recursive)
NSRecursiveLock
NSConditionLock
@synchronized
如何选择?

开发方便考虑 GCD信号量,串行线程。

面向对象考虑 NSLock,NSRecursiveLock,NSCondition,NSConditionLock

追求性能考虑 os_unfair_lock

代码简单考虑 @synchronized

自旋锁互斥锁对比

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

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

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

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

iOS读写安全方案

读写安全,需要多读单写。常见的方案有:

pthread_rwlock:读写锁
dispatch_barrier_async:异步栅栏调用

pthread_rwlock

等待的线程会进入休眠

pthread_rwlock_t lock;
// 初始化锁
pthread_rwlock_init(&lock, NULL);

// 读 加锁
pthread_rwlock_rdlock(&lock);

// 读 尝试加锁
pthread_rwlock_tryrdlock(&lock);

// 写 加锁
pthread_rwlock_wrlock(&lock);

// 写 尝试加锁
pthread_rwlock_trywrlock(&lock);

// 解锁
pthread_rwlock_unlock(&lock);

// 销毁
pthread_rwlock_destroy(&_lock);

dispatch_barrier_async

这个函数传入的并发队列必须是自己通过dispatch_queue_cretate创建的

如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果

dispatch_queue_t queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);

// 读
dispatch_async(queue, ^{
   
});
        
// 写
dispatch_barrier_async(queue, ^{

});

相关文章

  • iOS多线程 NSOperation

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程 pthread、NSThread

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程: GCD

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程运用

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程基础

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • 多线程介绍

    一、进程与线程 进程介绍 线程介绍 线程的串行 二、多线程 多线程介绍 多线程原理 多线程的优缺点 多线程优点: ...

  • iOS进阶之多线程管理(GCD、RunLoop、pthread、

    深入理解RunLoopiOS多线程--彻底学会多线程之『GCD』iOS多线程--彻底学会多线程之『pthread、...

  • iOS多线程相关面试题

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

  • 多线程之--NSOperation

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

  • iOS多线程之--NSThread

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

网友评论

      本文标题:多线程

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