首页 > 代码库 > iOS 多线程之NSThread简单使用

iOS 多线程之NSThread简单使用

  1、线程的构建和开启:

(1)_thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(threadOneMethod) object:nil];
    _thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(threadTwoMethod) object:nil];
    
    [_thread1 start];
    [_thread2 start];
先构建,然后开启,好处是可以控制开启的时间,并且可以得到NSThread对象,便于之后和这个线程通讯。
  (2) [NSThread detachNewThreadSelector:@selector(threadOneMethod) toTarget:self withObject:nil];
  (3) [self performSelectorInBackground:@selector(threadOneMethod) withObject:nil];
还有一种是从系统的NSThread继承一个子类,这个子类的构建和父类一样,但是线程内执行的代码是写在-(void )main方法里面的:
@interface MyThread : NSThread //从NSThread继承
@end

////////////////////
-(void )main{
    for (int i = 0; i<10; i++) {
        NSLog(@"myThread count: %d",i);
        sleep(1);
    }
    NSLog(@"thread end");
}

// 使用自定义的线程对象
_thread3 = [[MyThread alloc]init];
    [_thread3 start];
相对而言,前面3个方法构建的线程,线程内执行的方法都是在当前的类文件里面的,而使用main函数,线程中执行的方法是属于线程对象本身的,这样可以在任何其他需要使用这个线程方法的地方使用,而不用在实现一次方法。


2、线程同步,即线程锁的使用:

  (1)@synchronized关键词: 

-(void )threadOneMethod{
    @synchronized(@"lock"){  
        for (int i= 0; i<10; i++) { //循环10次,每次输出一个标识,然后线程沉睡一秒
            NSLog(@"111");
            sleep(1); 
        }
    }
    NSLog(@"thread1 end");
}
///////////////////////

-(void )threadTwoMethod{
    @synchronized(@"lock"){
        for (int i= 0; i<5; i++) { //循环5次,每次输出一个标识222,然后线程沉睡1秒
            NSLog(@"222");
            sleep(1);
        }
    }
    NSLog(@"thread2 end");
}
上面的例子是结合第一种线程构建方法的,两个线程几乎同时开启,但是第一个更快一些,执行后效果是,第一个方法循环输出10次“111”结束后,第二个方法才输出“222”。因为第一个方法开始进入@synchronized(@"lock")里面之后,第二个方法在执行到@synchronized(@"lock")这里就进不去了,会等到前面一个线程从里面出来,它才能继续向下进行,这就是线程锁的作用。有些时候多个线程会同时操作用同一个数据或对象,可能会出现意想不到的糟糕情况,会使用线程锁让某个线程在操作的时候,其他线程无法操作,从而保证线程安全。

@synchronized(@"lock")是使用括号里面的对象来区分的,如果把第二个方法里的对象改掉,变成@synchronized(@"111111"),就不会起到锁的作用了,因为这是它们就是两个完全不相关的锁了。

  2、NSLock:

[_lock lock];
        for (int i= 0; i<10; i++) {
            NSLog(@"111");
            sleep(1);
        }
 [_lock unlock];
核心的方法部分没有区别,只是把锁换成了[_lock lock]……[_lock unlock];这里的锁是依靠_lock这个NSLock对象来识别的,就是说到某个NSLock对象调用了lock方法后,其他线程使用同一个NSLock对象调用lock方法就会停滞不前进,知道同一个NSLock对象再调用unlock方法。

   这个过程有点像有些体检项目,房间里同时只能体检一个人,一个人进去后就会把门锁上,这时有其他人来了也只能等着,等到里面的人好了出来,门打开,下一个才能进去。


3、线程间通讯:

   副线程给主线程发消息:

[self performSelectorOnMainThread:@selector(mainThreadMethod) withObject:nil waitUntilDone:NO];

self是方法的调用者,mainThreadMethod是让主线程调用的方法,然后这个方法可以带参数,也就是object,最后的waitUntilDone是指是否等到主线程把方法执行完了,这个performSelector方法才返回。

 向其他任意线程发消息:
[self performSelector:@selector(timerFire) onThread:_thread1 withObject:nil waitUntilDone:NO];
主要是这里要传入一个NSThread对象用于指定在哪个线程执行,这也是前面构建线程的时候保留线程对象的意义。

然后,需要注意的是,这些performSelector形式的方法,都需要对方线程的RunLoop处于开启状态,因为这些方法实质是runloop的输入源,把消息发送给对方线程的runloop,然后对方从runloop里面获取消息,才去执行方法。主线程的runloop是默认开启的,副线程的runloop是默认构建,但是需要手动开启。

4、线程的关闭:

  需要关闭某个线程,可以给这个线程发消息,使他关闭:

- (IBAction)killThread:(UIButton *)sender {

    [self performSelector:@selector(exitThread:) onThread:_thread1 withObject:_thread1 waitUntilDone:NO];
}
////////////////

-(void)exitThread:(NSThread *)thread{
    NSLog(@"thread EXIT1");
    [NSThread exit];
    NSLog(@"thread EXIT2");
}
也可以:
<span style="font-size:14px;">-(void )threadOneMethod{
    //前面写线程需要执行任务的代码,最后进入runloop循环,保持线程不结束同时保持接受消息
    
    NSRunLoop *theRL = [NSRunLoop currentRunLoop];
    while (shouldKeepRunning  ){//通过</span><span style="font-family: Arial, Helvetica, sans-serif;"><span style="font-size:12px;">shouldKeepRunning判断是否继续进行循环,如果为NO,就会停止循环,然后继续向下运行,线程自然结束</span></span><span style="font-size:14px;">        NSLog(@"looprun");
        [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    };

    NSLog(@"thread1 end");
}
///////////

BOOL shouldKeepRunning = YES;//一个全局的BOOL类型变量
- (IBAction)killThread:(UIButton *)sender {//点击按钮,修改</span><span style="font-family: Arial, Helvetica, sans-serif;"><span style="font-size:12px;">shouldKeepRunning为NO</span></span><span style="font-size:14px;">    shouldKeepRunning = NO;
    
}</span>
这里是通过一个全局变量的改变来控制线程的继续还是结束。但是有个小问题是,当线程的runloop接受了外来的输入源之后,例如其他线程调用:
[self performSelector:@selector(timerFire) onThread:_thread1 withObject:nil waitUntilDone:NO];
在这个线程运行,runloop接受到消息后会阻塞在方法[theRLrunMode:NSDefaultRunLoopModebeforeDate:[NSDatedistantFuture]]里面,也即是说while循环不会继续向下一个循环进行,那么改变shouldKeepRunning就不能马上得到反馈,所以需要使用:
BOOL shouldKeepRunning = YES;
- (IBAction)killThread:(UIButton *)sender {
    [self performSelector:@selector(exitThread:) onThread:_thread1 withObject:nil waitUntilDone:NO];
    
}
/////////结合
-(void)exitThread:(NSThread *)thread{
    shouldKeepRunning = NO;
}
这样就是给要关闭的线程发消息,会立刻唤醒目标线程的runloop,因为[theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]方法的特性是在接触到输入源后方法立刻返回,这样while循环就会立刻进入进入下一个循环,也就会进行循环条件的判断,然后因为shouldKeepRunning变为NO了,就会退出循环,然后线程结束。

 两种线程结束的方法,貌似第二种更自然些,具体对于线程内部的运行和内存的管理上的影响,还得研究下。