多线程(1)

本文详细介绍了NSRunLoop和NSThread的原理与应用,包括RunLoop的创建、退出机制及线程的创建、阻塞与取消方法。同时探讨了RunLoop与线程的关系,以及多线程下数据安全的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、NSRunLoop

1.1 介绍

RunLoop从字面上解析,就是一直循环的跑,实际上它也是在一直在跑。通常来说,一个线程执行完一个任务后,线程就会退出销毁。但是我们可以通过RunLoop操作,使该线程常驻,在有任务的时候唤醒线程执行相应的任务,在没有任务执行的时候保存睡眠状态,时刻准备着任务的呼唤。

1.2 语法

1、主线程相关联的RunLoop创建

CFRunLoopRef源码

   // 创建字典
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        // 创建主线程 根据传入的主线程创建主线程对应的RunLoop
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        // 保存主线程 将主线程-key和RunLoop-Value保存到字典中
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
2、 创建与子线程相关联的RunLoop

CFRunLoopRef源码

    // 从字典中获取子线程的runloop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    if (!loop) {
        // 如果子线程的runloop不存在,那么就为该线程创建一个对应的runloop
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        // 把当前子线程和对应的runloop保存到字典中
    if (!loop) {
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&loopsLock);
    CFRelease(newLoop);
    }

从上面的代码可以看出,线程和 RunLoop 之间是一一对应的,其关系是保存在一个 Dictionary 里。所以我们创建子线程RunLoop时,只需在子线程中获取当前线程的RunLoop对象即可[NSRunLoop currentRunLoop];如果不获取,那子线程就不会创建与之相关联的RunLoop,并且只能在一个线程的内部获取其 RunLoop
[NSRunLoop currentRunLoop];方法调用时,会先看一下字典里有没有存子线程相对用的RunLoop,如果有则直接返回RunLoop,如果没有则会创建一个,并将与之对应的子线程存入字典中。
RunLoop 的销毁发生在线程结束时。

3、RunLoop退出
  1. 主线程销毁RunLoop退出
  2. Mode中有一些Timer 、Source、 Observer,这些保证Mode不为空时保证RunLoop没有空转并且是在运行的,当Mode中为空的时候,RunLoop会立刻退出
  3. 我们在启动RunLoop的时候可以设置什么时候停止
[NSRunLoop currentRunLoop]runUntilDate:<#(nonnull NSDate *)#>
[NSRunLoop currentRunLoop]runMode:<#(nonnull NSString *)#> beforeDate:<#(nonnull NSDate 

RunLoop处理逻辑

1、通知观察者 run loop 已经启动

2、通知观察者将要开始处理Timer事件

3、通知观察者将要处理非基于端口的Source0

4、启动准备好的Souecr0

5、如果基于端口的源Source1准备好并处于等待状态,立即启动:并进入步骤9

6、通知观察者线程进入休眠

7、将线程置于休眠直到任一下面的事件发生
(1)某一事件到达基于端口的源
(2)定时器启动
(3)Run loop 设置的时间已经超时
(4)run loop 被显式唤醒

8、通知观察者线程将被唤醒

9、处理未处理的事件,跳回2
(1)如果用户定义的定时器启动,处理定时器事件并重启 run loop。进入步骤 2
(2)如果输入源启动,传递相应的消息
(3)如果 run loop 被显式唤醒而且时间还没超时,重启 run loop。进入步骤 2

10、通知观察者run loop 结束

1.3注意点

1.每条线程都有唯一的与之对应的RunLoop对象。
2.主线程的RunLoop已经创建好了,而子线程的需要手动创建。(也就是说子线程的RunLoop默认是关闭的,因为有时候开了个线程但却没有必要开一个RunLoop,不然反而浪费了资源。 )

// main.m
int res = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
// 永远运行不到这里
// 在UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]))这个函数的内部就已经启动了一个RunLoop,所以函数一直没有返回,这才使得程序保持运行。
NSLog(@"-----");
return res;

3.RunLoop在第一次获取时创建,在线程结束时销毁。(这就相当于 线程是一个类,RunLoop是类里的实例变量,这样便于理解)

2、NSThread

2.1 介绍

一个NSThread对象就代表一条线程

2.2 语法

创建、启动线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];// 启动线程,在线程thread中执行self的run方法

//创建线程后自动启动线程
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"XL"];
//隐式创建并启动线程
[self performSelectorInBackground:@selector(run:) withObject:@"OC"];

优点:简单快捷
缺点:无法对线程进行更详细的设置

阻塞(暂停)线程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 调用这个方法的线程进入阻塞状态,
[NSThread sleepForTimeInterval:2];
//第二种线程暂停睡眠的方法
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
[NSThread sleepUntilDate:[NSDate distantFuture]];
强制停止线程 ,

调用之后会立即终止线程,即使任务还没有执行完成也会中断.不推荐使用此方式退出子线程,可能会造成内存泄漏

+ (void)exit;// 这是一个类方法,调用这个方法的所在线程会进入死亡状态
[NSThread exit];
取消线程
  • NSThread提供了一个cancel方法,和一个cancelled属性
  • cancel方法只是让isCancelled属性值置为YES,修改了线程状态,并不会停止/退出线程,任务会继续执行除非你做了一些其他操作
  • 我们可以监听isCancelled值的变化,做出相应的调整,如下面示例代码中,在子线程任务中监听cancel状态,如果cancel == yes , 回收内存等资源,然后return或者调用exit退出线程,随后线程就会销毁
- (void)createThreadCancel
{
    XLThread *thread = [[XLThread alloc] initWithTarget:self selector:@selector(runCancel) object:@"XL"];
    thread.name = @"cancel-thread";
    [thread start];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"before thread.isCancelled = %zd",thread.isCancelled);
        [thread cancel];
        NSLog(@"after thread.isCancelled = %zd",thread.isCancelled);
    });
}

- (void)runCancel
{
    for (NSInteger i = 0; i<10000; i++) {
        NSLog(@"-----%zd , [NSThread currentThread] = %@", i , [NSThread currentThread]);
        sleep(0.01);
        if ([NSThread currentThread].isCancelled) {
            // 进行线程退出前的一些操作,如内存回收等
            [NSThread exit]; // 线程退出,其后面的代码不会被执行 , 或者调用return;
            NSLog(@"-----exit [NSThread currentThread] = %@" , [NSThread currentThread]);
        }
    }
}

2.3 注意点

多线程的安全隐患

解决方法:互斥锁,@synchronized(锁对象) { // 需要锁定的代码 },注意:锁定1份代码只用1把锁,用多把锁是无效的,必须是同一个变量,虽然这个变量可以是任意类型的。

互斥锁的优缺点

优点:能有效防止因多线程抢夺资源造成的数据安全问题

缺点:需要消耗大量的CPU资源

互斥锁的使用前提:多条线程抢夺同一块资源,如果没有抢夺资源,就不要加锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值