多线程

2021-09-11  本文已影响0人  浅墨入画

线程和进程

线程
进程

进程中包含多个线程,进程负责任务的调度,线程负责任务的执行。在iOS中并不支持多进程,所有程序都是单一进程运行,进程之间相互独立

线程与进程的关系

两者的使用特点:

  1. 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但一个线程崩溃会导致整个进程都死掉。所以多进程要比多线程健壮
  2. 进程切换时消耗的资源大效率高。所以涉及到频繁的切换时,使用线程要好于进程。如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程;
  3. 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制;
  4. 线程是处理器调度的基本单位,但是进程不是;
  5. 线程没有地址空间,线程包含在进程地址空间中;
线程局部存储TLS

线程局部存储全称:Thread Local Storage:线程是没有地址空间的,但是存在线程局部存储。线程局部存储是某些操作系统为线程单独提供的私有空间,但通常只具有有限的容量
类的加载篇章中分析在objc源码中,_objc_init方法中包含了对tls的初始化操作,源码如下

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    // 线程绑定,例如线程数据的析构函数
    tls_init();
    static_init();
    runtime_init();
    exception_init();
#if __OBJC2__
    cache_t::init();
#endif
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

//tls_init源码实现
void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}

多线程原理

iOS中多线程同时执行的本质是CPU在多个任务之间快速的切换,由于CPU调度线程的时间足够快,就造成了多线程的“同时”执行的效果。其中切换的时间间隔就是时间片。所以多线程并不是真正的并发,而真正的并发必须建立在多核CPU的基础上。

多线程意义
  1. 能适当提高程序的执行效率
  2. 能适当提高资源的利用率,如CPU、内存
  3. 线程上的任务执行完成后,线程会自动销毁
  1. 开启线程需要占用一定的内存空间,默认情况下,每一个线程占用512KB
  2. 如果开启大量线程,会占用大量的内存空间,降低程序的性能
  3. 线程越多,CPU在调用线程上的开销就越大
  4. 程序设计更加复杂,比如线程间的通信多线程的数据共享
时间片

时间片的概念:CPU在多个任务之间进行快速的切换,这个时间间隔就是时间片

多线程官方文档

image.png

以上四种方案的简单示例

// *********1: pthread*********
pthread_t threadId = NULL;
//c字符串
char *cString = "HelloCode";
/**
 pthread_create 创建线程
 参数:
 1. pthread_t:要创建线程的结构体指针,通常开发的时候,如果遇到 C 语言的结构体,类型后缀 `_t / Ref` 结尾
 同时不需要 `*`
 2. 线程的属性,nil(空对象 - OC 使用的) / NULL(空地址,0 C 使用的)
 3. 线程要执行的`函数地址`
 void *: 返回类型,表示指向任意对象的指针,和 OC 中的 id 类似
 (*): 函数名
 (void *): 参数类型,void *
 4. 传递给第三个参数(函数)的`参数`
 */
int result = pthread_create(&threadId, NULL, pthreadTest, cString);
if (result == 0) {
    NSLog(@"成功");
} else {
    NSLog(@"失败");
}
    
//*********2、NSThread*********
[NSThread detachNewThreadSelector:@selector(threadTest) toTarget:self withObject:nil];
    
//*********3、GCD*********
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [self threadTest];
});
    
//*********4、NSOperation*********
[[[NSOperationQueue alloc] init] addOperationWithBlock:^{
    [self threadTest];
}];

/**
 1. 循环的执行速度很快
 2. 栈区/常量区的内存操作也挺快
 3. 堆区的内存操作有点慢
 4. I(Input输入) / O(Output 输出) 操作的速度是最慢的!
 * 会严重的造成界面的卡顿,影响用户体验!
 * 多线程:开启一条线程,将耗时的操作放在新的线程中执行
 */
- (void)threadTest{
    NSLog(@"begin");
    NSInteger count = 1000 * 100;
    for (NSInteger i = 0; i < count; i++) {
        // 栈区
        NSInteger num = i;
        // 常量区
        NSString *name = @"zhang";
        // 堆区
        NSString *myName = [NSString stringWithFormat:@"%@ - %zd", name, num];
        NSLog(@"%@", myName);
    }
    NSLog(@"over");
}

void *pthreadTest(void *para){
    // 接 C 语言的字符串
    //    NSLog(@"===> %@ %s", [NSThread currentThread], para);
    // __bridge 将 C 语言的类型桥接到 OC 的类型
    NSString *name = (__bridge NSString *)(para);
    
    NSLog(@"===>%@ %@", [NSThread currentThread], name);
    
    return NULL;
}
C与OC的桥接

线程生命周期

线程的生命周期主要分为五部分:新建 - 就绪 - 运行 - 阻塞 - 死亡

线程生命周期
线程池原理
线程池原理

多线程面试题

任务执行速度的影响因素有哪些?

这个问题从四个角度分析:CPU的调度情况任务的复杂度任务的优先级线程状态
目前iOS中,线程优先级的threadPriority属性已经弃用,被NSQualityOfService类型的qualityOfService所代替,看先底层的枚举设置

typedef NS_ENUM(NSInteger, NSQualityOfService) {
    NSQualityOfServiceUserInteractive = 0x21,
    NSQualityOfServiceUserInitiated = 0x19,
    NSQualityOfServiceUtility = 0x11,
    NSQualityOfServiceBackground = 0x09,
    NSQualityOfServiceDefault = -1
} API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));

开发者自己指定NSQualityOfService服务质量,用于表示工作的性质和对系统的重要性。当存在资源竞争时,使用高质量的服务类比使用低质量的服务类获得更多的资源

优先级反转
优先级的影响因素

自旋锁和互斥锁

当多个线程同时访问同一块资源时,很容易引发资源抢夺,造成数据错乱数据安全问题,有以下两种解决方案:

例如多窗口卖票时,会产生资源的抢夺(如下图)。这时我们的常规操作就是加锁

image.png
互斥锁

使用互斥锁的注意事项:

自旋锁
自旋锁与互斥锁
使用场景
atomic原子锁与nonatomic非原子锁的作用

atomicnonatomic主要用于属性的修饰

  1. 仅仅在属性的setter方法中,增加了锁(自旋锁),能够保证同一时间,只有一条线程对属性进行写操作
  2. 同一时间 单(线程)写多(线程)读的线程处理技术
  3. Mac开发中常用
iOS开发的建议
atomic与nonatomic的区别
  1. 非原子属性
  2. 非线程安全,适合内存小的移动设备
  1. 原子属性(线程安全),针对多线程设计的,默认值
  2. 保证同一时间只有一个线程能够写入(但是同一个时间多个线程都可以取值)
  3. atomic 本身就有一把锁(自旋锁) 单写多读:单个线程写入,多个线程可以读取
  4. 线程安全,需要消耗大量的资源
objc4-818.2源码分析
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{
    bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
    bool mutableCopy = (shouldCopy == MUTABLE_COPY);
    reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }
    // atomic修饰,增加了spinlock_t的锁操作;
    // 所以atomic是标示,自身并不是锁。而atomic所谓的自旋锁,由底层代码实现。
    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}
线程与Runloop的关系
线程间通讯

Threading Programming Guide文档,线程间的通讯有以下几种方式

线程间通讯
上一篇 下一篇

猜你喜欢

热点阅读