美文网首页
十九、多线程

十九、多线程

作者: Mjs | 来源:发表于2020-11-02 11:32 被阅读0次

线程

  • 线程是进程的基本执⾏单元,⼀个进程的所有任务都在线程中执⾏
  • 进程要想执⾏任务,必须得有线程,进程⾄少要有⼀条线程
  • 程序启动会默认开启⼀条线程,这条线程被称为主线程或 UI 线程

进程

  • 进程是指在系统中正在运⾏的⼀个应⽤程序
  • 每个进程之间是独⽴的,每个进程均运⾏在其专⽤的且受保护的内存空间内
  • 通过“活动监视器”可以查看 Mac 系统中所开启的进程

进程和线程的关系

  • 地址空间:同⼀进程的线程共享本进程的地址空间,⽽进程之间则是独⽴的地址空间。
  • 资源拥有:同⼀进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的
    资源是独⽴的。
  1. ⼀个进程崩溃后,在保护模式下不会对其他进程产⽣影响,但是⼀个线程崩溃整个进
    程都死掉。所以多进程要⽐多线程健壮。
  2. 进程切换时,消耗的资源⼤,效率⾼。所以涉及到频繁的切换时,使⽤线程要好于进
    程。同样如果要求同时进⾏并且⼜要共享某些变量的并发操作,只能⽤线程不能⽤进程
  3. 执⾏过程:每个独⽴的进程有⼀个程序运⾏的⼊⼝、顺序执⾏序列和程序⼊⼝。但是
    线程不能独⽴执⾏,必须依存在应⽤程序中,由应⽤程序提供多个线程执⾏控制。
  4. 线程是处理器调度的基本单位,但是进程不是。
  5. 线程没有地址空间,线程包含在进程地址空间中

多线程

  • 优点
    • 能适当提⾼程序的执⾏效率
    • 能适当提⾼资源的利⽤率(CPU,内存)
    • 线程上的任务执⾏完成后,线程会⾃动销毁
  • 缺点
    • 开启线程需要占⽤⼀定的内存空间(默认情况下,每⼀个线程都占 512 KB)
    • 如果开启⼤量的线程,会占⽤⼤量的内存空间,降低程序的性能
    • 线程越多,CPU 在调⽤线程上的开销就越⼤
    • 程序设计更加复杂,⽐如线程间的通信、多线程的数据共享

时间⽚的概念:

CPU在多个任务直接进⾏快速的切换,这个时间间隔就是时间⽚

  • (单核CPU)同⼀时间,CPU 只能处理 1 个线程
    • 换⾔之,同⼀时间只有 1 个线程在执⾏
  • 多线程同时执⾏:
    • 是 CPU 快速的在多个线程之间的切换
    • CPU 调度线程的时间⾜够快,就造成了多线程的“同时”执⾏的效果
  • 如果线程数⾮常多
    • CPU 会在 N 个线程之间切换,消耗⼤量的 CPU 资源
    • 每个线程被调度的次数会降低,线程的执⾏效率降低
多线程技术方案.png
    //0: pthread
    /**
     pthread_create 创建线程
     参数:
     1. pthread_t:要创建线程的结构体指针,通常开发的时候,如果遇到 C 语言的结构体,类型后缀 `_t / Ref` 结尾
     同时不需要 `*`
     2. 线程的属性,nil(空对象 - OC 使用的) / NULL(空地址,0 C 使用的)
     3. 线程要执行的`函数地址`
     void *: 返回类型,表示指向任意对象的指针,和 OC 中的 id 类似
     (*): 函数名
     (void *): 参数类型,void *
     4. 传递给第三个参数(函数)的`参数`
     
     返回值:C 语言框架中非常常见
     int
     0          创建线程成功!成功只有一种可能
     非 0       创建线程失败的错误码,失败有多种可能!
     */
    
    // 1: pthread
    pthread_t threadId = NULL;
    //c字符串
    char *cString = "HelloCode";
//    NSString *ocString = @"Gavin";
    //延伸到: OC--C的混编 尤其在智能家居,SDK封装
    //抛出一个问题: 在ARC需要这样操作,在MRC不需要
    // OC prethread -- 跨平台
    // 锁
    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];
    }];
    

C与OC的桥接

  • __bridge只做类型转换,但是不修改对象(内存)管理权;
  • __bridge_retained(也可以使⽤CFBridgingRetain)将Objective-C的对象转换为
    Core Foundation的对象,同时将对象(内存)的管理权交给我们,后续需要使⽤
    CFRelease或者相关⽅法来释放对象;
  • __bridge_transfer(也可以使⽤CFBridgingRelease)将Core Foundation的对象
    转换为Objective-C的对象,同时将对象(内存)的管理权交给ARC。

优先级问题

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));

并非优先级越快速度越快,还要看资源的大小和任务的复杂度

互斥锁

  • 互斥锁⼩结
    • 保证锁内的代码,同⼀时间,只有⼀条线程能够执⾏!
    • 互斥锁的锁定范围,应该尽量⼩,锁定范围越⼤,效率越差!
  • 互斥锁参数
    • 能够加锁的任意 NSObject 对象
    • 注意:锁对象⼀定要保证所有的线程都能够访问
    • 如果代码中只有⼀个地⽅需要加锁,⼤多都使⽤ self,这样可以避免单独再创建⼀个
      锁对象

自旋锁是一种互斥锁的实现方式而已,相比一般的互斥锁会在等待期间放弃cpu,自旋锁(spinlock)则是不断循环并测试锁的状态,这样就一直占着cpu。

互斥锁:用于保护临界区,确保同一时间只有一个线程访问数据。对共享资源的访问,先对互斥量进行加锁,如果互斥量已经上锁,调用线程会阻塞,直到互斥量被解锁。在完成了对共享资源的访问后,要对互斥量进行解锁。

临界区:每个进程中访问临界资源的那段程序称为临界区,每次只允许一个进程进入临界区,进入后不允许其他进程进入。

自旋锁:与互斥量类似,它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等(自旋)阻塞状态。用在以下情况:锁持有的时间短,而且线程并不希望在重新调度上花太多的成本。"原地打转"。

自旋锁与互斥锁的区别:线程在申请自旋锁的时候,线程不会被挂起,而是处于忙等的状态。

信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

atomic与nonatomic 的区别

nonatomic ⾮原⼦属性
atomic 原⼦属性(线程安全),针对多线程设计的,默认值
保证同⼀时间只有⼀个线程能够写⼊(但是同⼀个时间多个线程都可以取值)
atomic 本身就有⼀把锁(⾃旋锁)
单写多读:单个线程写⼊,多个线程可以读取
atomic:线程安全,需要消耗⼤量的资源
nonatomic:⾮线程安全,适合内存⼩的移动设备
iOS 开发的建议
所有属性都声明为 nonatomic
尽量避免多线程抢夺同⼀块资源
尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减⼩移动客户端的压⼒


@interface ViewController ()
/**
 atomic     是原子属性,是为多线程开发准备的,是默认属性!
            仅仅在属性的 `setter` 方法中,增加了锁(自旋锁),能够保证同一时间,只有一条线程对属性进行`写`操作
            同一时间 单(线程)写多(线程)读的线程处理技术
 nonatomic  是非原子属性
            没有锁!性能高!
 */
@property (nonatomic, copy)   NSString *name;
@end

// 在 OC 中,如果同时重写 了 setter & getter 方法,系统不再提供 _成员变量,需要使用合成指令
// @synthesize name 取个别名:_name
@synthesize name = _name;
#pragma mark - 模拟原子属性示例代码
- (NSString *)name {
    return _name;
}
- (void)setName:(NSString *)name {
    /**
     * 增加一把锁,就能够保证一条线程在同一时间写入!
     */
    @synchronized (self) {
        _name = name;
    }
}

线程间通信

线程间通信常用方法

  • (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

  • (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;

属性的weak、strong

从xib拖出来的控件是已经被持有了所以用weak
直接创建后直接被清空了

_imageView = [[UIImageView alloc] init];

如果用中间变量持有会维持到作用域结束

UIImageView imageView = [[UIImageView alloc] init];
_imageView = imageView

相关文章

网友评论

      本文标题:十九、多线程

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