1. 进程(process):

        系统中正在运行的一个程序的实例,具有一定的独立功能,是线程的容器。

2.线程(thread):

        线程是进程执行的最小单位,一个进程中至少包含一个线程(主线程),进程中任务都在线程中执行(主线程或子线程)。

       系统中正在运行的每一个应用程序都是一个 进程(Process) ,每个进程系统都会分配给它独立的内存运行。也就是说,在iOS系统中中,每一个应用都是一个进程。

       一个进程的所有任务都在 线程(Thread) 中进行,因此每个进程至少要有一个线程,也就是主线程。那多线程其实就是一个进程开启多条线程,让所有任务并发执行。

       iOS App一旦运行,默认就会开启一条线程。这条线程,通常称作为“主线程”。在iOS应用中主线程的作用一般是:
刷新UI;
处理UI事件,例如点击、滚动、拖拽。

       如果主线程的操作太多、太耗时,就会造成App卡顿现象严重。所以,通常我们都会把耗时的操作放在子线程中进行,获取到结果之后,回到主线程去刷新UI。

       多线程在一定意义上实现了进程内的资源共享,以及效率的提升。同时,在一定程度上相对独立,它是程序执行流的最小单元,是进程中的一个实体,是执行程序最基本的单元,有自己栈和寄存器。

3.多线程

       同一时间,CPU只能处理一条线程,也就是只有一条线程在工作。所谓多线程并发(同时)执行,其实是CPU快速的在多线程之间调度(切换)。如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。

iOS多线程有四种方法:pthread,NSThread,GCD, NSOperation,这几种多线程的区别,如图1-1所示:

                                                                      图1-1    iOS四种线程对比

      选择合适的多线程事项很重要,简单而安全的选择NSOperation实现多线程即可。处理大量并发数据,又追求性能效率的选择GCD。

NSThread:

     NSThread是苹果官方提供面向对象操作线程的技术,简单方便,可以直接操作线程对象,不过需要自己控制线程的生命周期,管理所有的线程活动,如生命周期、线程同步、睡眠等。在平时使用很少,最常用到的无非就是 [NSThread currentThread]获取当前线程。每个NSThread对象对应一个线程,真正最原始的线程,相对简单。

    NSThread三种使用方式:

  • 动态创建
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething1:) object:(@"NSThread1")];
[thread1 start];
  • 静态创建
// 创建好之后直接启动
[NSThread detachNewThreadSelector:@selector(doSomething2:) toTarget:self withObject:(@"NSTread2")];
  • 隐式创建
// 创建好之后也是直接启动
[self performSelectorInBackground:@selector(doSomething3:) withObject:(@"NSTread3")];

线程间资源共享&线程加锁:

       在程序运行过程中,如果存在多线程,那么各个线程读写资源就会存在先后、同时读写资源的操作,因为是在不同线程,CPU调度过程中我们无法保证哪个线程会先读写资源,哪个线程后读写资源。因此为了防止数据读写混乱和错误的发生,我们要将线程在读写数据时加锁,这样就能保证操作同一个数据对象的线程只有一个,当这个线程执行完成之后解锁,其他的线程才能操作此数据对象。NSLock / NSConditionLock / NSRecursiveLock / @synchronized都可以实现线程上锁的操作。

  1. @synchronized

直接上例子:相信12306卖火车票的例子大家了解

首先:开启两个线程同时售票

self.tickets = 20;
    NSThread *t1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTickets) object:nil];
    t1.name = @"售票员A";
    [t1 start];
    
    NSThread *t2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTickets) object:nil];
    t2.name = @"售票员B";
    [t2 start];

然后:将售票的方法加锁
- (void)saleTickets{
    while (YES) {
        [NSThread sleepForTimeInterval:1.0];
        //互斥锁 -- 保证锁内的代码在同一时间内只有一个线程在执行
        @synchronized (self){
            //1.判断是否有票
            if (self.tickets > 0) {
                //2.如果有就卖一张
                self.tickets --;
                NSLog(@"还剩%d张票  %@",self.tickets,[NSThread currentThread]);
            }else{
                //3.没有票了提示
                NSLog(@"卖完了 %@",[NSThread currentThread]);
                break;
            }
        }
    }

}

NSLock:

      -(BOOL)tryLock;//尝试加锁,成功返回YES ;失败返回NO ,但不会阻塞线程的运行 -(BOOL)lockBeforeDate:(NSDate *)limit;//在指定的时间以前得到锁。YES:在指定时间之前获得了锁;NO:在指定时间之前没有获得锁。 该线程将被阻塞,直到获得了锁,或者指定时间过期。 - (void)setName:(NSString*)newName//为锁指定一个Name - (NSString*)name//**返回锁指定的**name @property (nullable, copy) NSString *name;线程锁名称

举个例子:

NSLock* myLock=[[NSLock alloc]init];
NSString *str=@"hello"; [NSThread detachNewThreadWithBlock:^{ [myLock lock]; NSLog(@"%@",str); str=@"world"; [myLock unlock]; }];
[NSThread detachNewThreadWithBlock:^{ [myLock lock]; 
NSLog(@"%@",str); str=@"变化了"; [myLock unlock]; }];

输出结果不加锁之前,两个线程输出一样 hello;加锁之后,输出分辨为hello 与world。

NSConditionLock:

使用此锁,在线程没有获得锁的情况下,阻塞,即暂停运行,典型用于生产者/消费者模型。

- (instancetype)initWithCondition:(NSInteger)condition;//初始化条件锁

- (void)lockWhenCondition:(NSInteger)condition;//加锁 (条件是:锁空闲,即没被占用;条件成立)

- (BOOL)tryLock; //尝试加锁,成功返回TRUE,失败返回FALSE

- (BOOL)tryLockWhenCondition:(NSInteger)condition;//在指定条件成立的情况下尝试加锁,成功返回TRUE,失败返回FALSE - (void)unlockWithCondition:(NSInteger)condition;//在指定的条件成立时,解锁

- (BOOL)lockBeforeDate:(NSDate *)limit;//在指定时间前加锁,成功返回TRUE,失败返回FALSE,

- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;//条件成立的情况下,在指定时间前加锁,成功返回TRUE,失败返回FALSE, @property (readonly) NSInteger condition;//条件锁的条件 @property (nullable, copy) NSString *name;//条件锁的名称

举个例子:

NSConditionLock* myCondition=[[NSConditionLock alloc]init];

[NSThread detachNewThreadWithBlock:^{

for(int i=0;i<5;i++) { 

[myCondition lock]; 

NSLog(@"当前解锁条件:%d",i);

sleep(2);

[myCondition unlockWithCondition:i]; 

BOOL isLocked=[myCondition tryLockWhenCondition:2];

if(isLocked) { 

NSLog(@"加锁成功!!!!!"); [myCondition unlock]; 

} } }];

输出结果,在条件2 解锁之后,等待条件2 的锁加锁成功。

NSRecursiveLock:

此锁可以在同一线程中多次被使用,但要保证加锁与解锁使用平衡,多用于递归函数,防止死锁。

- (BOOL)tryLock;//尝试加锁,成功返回TRUE,失败返回FALSE - (BOOL)lockBeforeDate:(NSDate *)limit;//在指定时间前尝试加锁,成功返回TRUE,失败返回FALSE @property (nullable, copy) NSString *name;//线程锁名称

使用示例:

-(void)initRecycle:(int)value { 

[myRecursive lock];

if(value>0) { NSLog(@"当前的value值:%d",value);

sleep(2);

[self initRecycle:value-1]; 

}

[myRecursive unlock]; 

}

输出结果: 从你传入的数值一直到1,不会出现死锁

线程安全之原子属性 atomic:

        原子属性(线程安全)与非原子属性,平时我们@property声明对象属性时会用到nonatomic,是什么意思呢?

        苹果系统在我们声明对象属性时默认是atomic,也就是说在读写这个属性的时候,保证同一时间内只有一个线程能够执行。当声明时用的是atomic,通常会生成 _成员变量 如果同时重写了getter&setter _成员变量 就不自动生成。实际上原子属性内部有一个锁,叫做“自旋锁”。

首先我们比较一下“自旋锁” & “互斥锁”的异同,然后回答上面的问题

  • 共同点

都能够保证线程安全

  • 不同点

互斥锁:如果其他线程正在执行锁定的代码,此线程就会进入休眠状态,等待锁打开;然后被唤醒

自旋锁:如果线程被锁在外面,哥么就会用死循环的方式一直等待锁打开!

无论什么锁,都很消耗性能,效率不高,所以在我们平时开发过程中,会使用nonatomic

@property (strong, nonatomic) NSObject *myNonatomic;

@property (strong, atomic) NSObject *myAtomic;

根据上面描述,我们得出结论,当我们重写了myAtomic的setter和getter方法

- (void)setMyAtomic:(NSObject *)myAtomic{ _myAtomic = myAtomic; }

- (NSObject *)myAtomic{ return _myAtomic; }

那么我们就必须声明一个_myAtomic静态变量

@synthesize myAtomic = _myAtomic;

否则系统在编译的时候找不到 _myAtomic

GCD创建多线程:

GCD为Grand Central Dispatch的缩写。Grand Central Dispatch (GCD)是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。

GCD的作用:

       GCD 可用于多核的并行运算;GCD 会自动利用更多的 CPU 内核(比如双核、四核);GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。

GCD任务和队列

任务: 表现上就是一段代码,OC就对应一个Block。有两种执行方式,是否会创建新的线程会不会阻塞当前线程

     GCD线程的同步执行(sync):在当前线程执行任务,不会开辟新的线程。必须等到Block函数执行完毕后,dispatch函数才会返回。

    GCD线程的异步执行(async):可以在新的线程中执行任务,但不一定会开辟新的线程。dispatch函数会立即返回, 然后Block在后台异步执行。

GCD线程的创建:

- (void)create {
    // dispatch_queue_create 第一个参数是队列名字,一般用app的Bundle Identifier命名方式命名;第二个参数为NULL时表是串行队列
    //串行队列
    dispatch_queue_t serialQueue = dispatch_queue_create("q1.andyron.com", NULL);
    dispatch_queue_t serialQueue2 = dispatch_queue_create("q2.andyron.com", DISPATCH_QUEUE_SERIAL);
    //并行队列
    dispatch_queue_t concurrentQueue = dispatch_queue_create("q3.andyron.com", DISPATCH_QUEUE_CONCURRENT);
    //全局并行队列    DISPATCH_QUEUE_PRIORITY_DEFAULT表示优先级
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //主队列获取
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
}

多线程的优点:

(1).能适当提高程序的执行效率
(2).能适当提高资源的利用率,这个利用率表现在(CPU,内存的利用率)

多线程的缺点:

      开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB,如果开启大量的线程,会占用大量的内存空间,降低程序的性能)

      线程越多,CPU在调度线程上的开销就越大

      线程越多,程序设计就越复杂,比如线程之间的通信,多线程的数据共享,这些都需要程序的处理,增加了程序的复杂度。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐