小知识点好东西线程

多线程-线程间通信、线程安全问题

2017-02-14  本文已影响224人  進无尽

前言

说到多线程同步问题就不得不提多线程中的锁机制,多线程操作过程中往往多个线程是并发执行的,同一个资源可能被多个线程同时访问,造成资源抢夺,这个过程中如果没有锁机制往往会造成重大问题。比如常见的车票的销售问题。


线程同步

所谓线程同步就是为了防止多个线程抢夺同一个资源造成的数据安全问题,所采取的一种措施。主要的方法有以下几种:

使用@synchronized解决线程同步问题相比较NSLock要简单一些,但是效率是众多锁中最差的。首先选择一个对象作为同步对象(一般使用self),然后将”加锁代码”(争夺资源的读取、修改代码)放到代码块中。 注意:锁定1份代码只用1把锁,用多把锁是无效的。使用互斥锁,在同一个时间,只允许一条线程执行锁中的代码.因为互斥锁的代价非常昂贵,所以锁定的代码范围应该尽可能小,只要锁住资源读写部分的代码即可。使用互斥锁也会影响并发的目的。

   @synchronized(self) {
     //1.先检查票数
        int count = leftTicketsCount;
        if (count>0) {
            //暂停一段时间
            [NSThread sleepForTimeInterval:0.002];
            //2.票数-1
            leftTicketsCount= count-1;
            //获取当前线程
            NSThread *current=[NSThread currentThread];
            NSLog(@"%@--卖了一张票,还剩余%d张票", current.name, leftTicketsCount);
        }
        else {
            //退出线程
            [NSThread exit];
        }
   }

iOS中对于资源抢占的问题可以使用同步锁NSLock来解决,使用时把需要加锁的代码(以后暂时称这段代码为”加锁代码“)放到NSLock的lock和unlock之间。

Paste_Image.png

同步锁时如果一个线程A已经加锁,线程B就无法进入。那么B怎么知道是否资源已经被其他线程锁住呢?可以通过tryLock方法,此方法会返回一个BOOL型的值,如果为YES说明获取锁成功,否则失败。

在GCD中提供了一种信号机制,也可以解决资源抢占问题(和同步锁的机制并不一样)。GCD中信号量是dispatch_semaphore_t类型,支持信号通知和信号等待。每当发送一个信号通知,则信号量+1;每当发送一个等待信号时信号量-1,;如果信号量为0则信号会处于等待状态,直到信号量大于0开始执行。根据这个原理我们可以初始化一个信号量变量,默认信号量设置为1,每当有线程进入“加锁代码”之后就调用信号等待命令(此时信号量为0)开始等待,此时其他线程无法进入,执行完后发送信号通知(此时信号量为1),其他线程开始进入执行,如此一来就达到了线程同步目的。

  dispatch_semaphore_t _semaphore;//定义一个信号量
 
  #pragma mark 请求图片数据
  -(NSData *)requestData:(int )index{
  NSData *data;
  NSString *name;

  # 信号等待
  # 第二个参数:等待时间

  dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
  if (_imageNames.count>0) {
      name=[_imageNames lastObject];
      [_imageNames removeObject:name];
  }
  //信号通知
  dispatch_semaphore_signal(_semaphore);
  if(name){
      NSURL *url=[NSURL URLWithString:name];
      data=[NSData dataWithContentsOfURL:url];
  }
  return data;
  }

NSCondition 的对象实际上作为一个锁和一个线程检查器:锁主要为了当检测条件时保护数据源,执行条件引发的任务;线程检查器主要是根据条件决定是否继续运行线程,即线程是否被阻塞。单纯解决线程同步问题不是NSCondition设计的主要目的,NSCondition更重要的是解决线程之间的调度关系(当然,这个过程中也必须先加锁、解锁)。NSCondition可以调用wati方法控制某个线程处于等待状态,直到其他线程调用signal(此方法唤醒一个线程,如果有多个线程在等待则任意唤醒一个)或者broadcast(此方法会唤醒所有等待线程)方法唤醒该线程才能继续。

  //初始化锁对象
  _condition=[[NSCondition alloc]init];

  #pragma mark 创建图片
  -(void)createImageName{
    [_condition lock];
    //如果当前已经有图片了则不再创建,线程处于等待状态
    if (_imageNames.count>0) {
        NSLog(@"createImageName wait, current:%i",_currentIndex);
        [_condition wait];
    }else{
        NSLog(@"createImageName work, current:%i",_currentIndex);
        //生产者,每次生产1张图片
        [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",_currentIndex++]];

        //创建完图片则发出信号唤醒其他等待线程
        [_condition signal];
    }
   [_condition unlock];
  }

iOS中的其他锁

在iOS开发中,除了同步锁有时候还会用到一些其他锁类型,在此简单介绍一下:

NSRecursiveLock:递归锁,有时候“加锁代码”中存在递归调用,递归开始前加锁,递归调用开始后会重复执行此方法以至于反复执行加锁代码最终造成死锁,这个时候可以使用递归锁来解决。使用递归锁可以在一个线程中反复获取锁而不造成死锁,这个过程中会记录获取锁和释放锁的次数,只有最后两者平衡锁才被最终释放。
NSDistributedLock:分布锁,它本身是一个互斥锁,基于文件方式实现锁机制,可以跨进程访问。
pthread_mutex_t:同步锁,基于C语言的同步锁机制,使用方法与其他同步锁机制类似。

有一张图片简单的比较了各种锁的加解锁性能:


Paste_Image.png

还有一种方式可以达到线程同步,那就是同步执行

PS:原子和非原子属性

atomic 的本意是指属性的存取方法是线程安全的,并不保证整个对象是线程安全的。比如setter函数里面改变两个成员变量,如果你用nonatomic的话,getter可能会取到只更改了其中一个变量时候的状态,这样取到的东西会有问题。
atomic:能够实现“单写多读”的数据保护,同一时间只允许一个线程修改属性值,但是允许多个线程同时读取属性值,在多线程读取数据时,有可能出现“脏”数据 - 读取的数据可能会不正确。原子属性是默认属性,atomic(原子属性)在setter方法内部加了一把自旋锁如果不需要考虑线程安全,要指定 nonatomic。

关于atomic的实现最开始的方式如下,我们可以看到其实现原理也是通过加锁实现的。

- (void)setCurrentImage:(UIImage *)currentImage
{
  @synchronized(self) {
  if (_currentImage != currentImage) {
      [_currentImage release];
      _currentImage = [currentImage retain];
      // do something
      }
  }
}
- (UIImage *)currentImage
{
  @synchronized(self) {
      return _currentImage;
  }
}

线程间通信

线程间通信用到的比较多的包括俩个方面: 其他线程向主线程的通信,其他俩个线程间的通信。

MyWorkerClass

#import "MyWorkerClass.h"
@interface MyWorkerClass() <NSMachPortDelegate> {
    NSPort *remotePort;
    NSPort *myPort;
  }
@end
#define kMsg1 100
#define kMsg2 101

@implementation MyWorkerClass

- (void)launchThreadWithPort:(NSPort *)port {


  @autoreleasepool {

    //1. 保存主线程传入的port
    remotePort = port;

    //2. 设置子线程名字
    [[NSThread currentThread] setName:@"MyWorkerClassThread"];

    //3. 开启runloop
    [[NSRunLoop currentRunLoop] run];

    //4. 创建自己port
    myPort = [NSPort port];

    //5.
    myPort.delegate = self;

    //6. 将自己的port添加到runloop
    //作用1、防止runloop执行完毕之后推出
    //作用2、接收主线程发送过来的port消息
    [[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];

    //7. 完成向主线程port发送消息
    [self sendPortMessage];

    }
}

/**
 *   完成向主线程发送port消息
 */
- (void)sendPortMessage {

    NSMutableArray *array  =[[NSMutableArray alloc]initWithArray:@[@"1",@"2"]];
//发送消息到主线程,操作1
[remotePort sendBeforeDate:[NSDate date]
                     msgid:kMsg1
                components:array
                      from:myPort
                  reserved:0];

    //发送消息到主线程,操作2
    //    [remotePort sendBeforeDate:[NSDate date]
    //                         msgid:kMsg2
    //                    components:nil
    //                          from:myPort
    //                      reserved:0];
}


#pragma mark - NSPortDelegate

/**
 *  接收到主线程port消息
 */
- (void)handlePortMessage:(NSPortMessage *)message
{
    NSLog(@"接收到父线程的消息...\n");

//    unsigned int msgid = [message msgid];
//    NSPort* distantPort = nil;
//
//    if (msgid == kCheckinMessage)
//    {
//        distantPort = [message sendPort];
//
//    }
//    else if(msgid == kExitMessage)
//    {
//        CFRunLoopStop((__bridge CFRunLoopRef)[NSRunLoop currentRunLoop]);
//    }
}
@end

另外Notification在多线程中的使用需要注意

Notification在多线程中只在同一个线程中POST和接收到消息,如果想实现,在一个线程中发通知,在另一个线程中接收到事件,需要用到通知的 重定向技术,这其中用到了进程中的通信。了解更多看这里Notification与多线程


本文参考文章:
IOS多线程开发其实很简单
iOS线程通信和进程通信的例子(NSMachPort和NSTask,NSPipe)
http://www.cnblogs.com/samyangldora/p/4631815.html

上一篇下一篇

猜你喜欢

热点阅读