美文网首页
多线程的安全问题

多线程的安全问题

作者: 开心一刻_ | 来源:发表于2016-11-29 16:05 被阅读0次
    多线程涉及到写操作就容易出现问题

    对指针本身赋值:

    self.userName = @"peak";
    

    访问指针指向的字符串所在的内存区域:

    [self.userName rangeOfString:@"peak"]
    

    property分为三类:
    pointer Property - > Memory
    Primitive Property

    内存的理解:

    我们只有一个地址总线,一个内存。即使在多线程的情况下,也不可能存在两个线程同时访问同一个块内存区域的场景。
    内存的访问是通过一个一个地址总线串行排列访问的,所以在继续后续之前,我们先明确几个结论:
    1)内存的访问是串行的,并不会导致多乱或者应用的crash
    2)bool,int,long类型是原子性的。

    多线程不安全的场景

    @property (atomic, assign)    int       intA;
     
    //thread A
    for (int i = 0; i < 10000; i ++) {
        self.intA = self.intA + 1;
        NSLog(@"Thread A: %d\n", self.intA);
    }
     
    //thread B
    for (int i = 0; i < 10000; i ++) {
        self.intA = self.intA + 1;
        NSLog(@"Thread B: %d\n", self.intA);
    }
    

    虽然intA声明为原子的,但是结果不一定是20000,因为self.intA = self.intA + 1;不是原子操作,虽然intA的setter和getter方法是原子操作,但是语句不是原子的,这行赋值代码包括读取load + 1(add),赋值(store)三部操作,当线程A store的时候可能线程B已经执行了若干次store了,最后结果小于预期的值。

    @property (atomic, strong) NSString*                 userName;
    - (void)setUserName:(NSString *)userName {
        if(_uesrName != userName) {
            [userName retain];
            [_userName release];
            _userName = userName;
        }
    }
    

    不仅仅是赋值操作,还会有retain,release调用。如果property为nonatomic,上述的setter方法就不是原子操作,我们可以假设一种场景,线程1先通过getter获取当前_userName,之后线程2通过setter调用[_userName release];,线程1所持有的_userName就变成无效的地址空间了,如果再给这个地址空间发消息就会导致crash,出现多线程不安全的场景。

    场景三
    @property (atomic, strong) NSString*                 stringA;
     
    //thread A
    for (int i = 0; i < 100000; i ++) {
        if (i % 2 == 0) {
            self.stringA = @"a very long string";
        }
        else {
            self.stringA = @"string";
        }
        NSLog(@"Thread A: %@\n", self.stringA);
    }
     
    //thread B
    for (int i = 0; i < 100000; i ++) {
        if (self.stringA.length >= 10) {
            NSString* subStr = [self.stringA substringWithRange:NSMakeRange(0, 10)];
        }
        NSLog(@"Thread B: %@\n", self.stringA);
    }
    

    虽然stringA是atomic的property,而且在取substring的时候做了length判断,线程B还是很容易crash,因为在前一刻读length的时候self.stringA = @"a very long string";,下一刻取substring的时候线程A已经将self.stringA = @"string";,立即出现out of bounds的Exception,crash,多线程不安全。

    @property (atomic, strong) NSArray*                 arr;
     
    //thread A
    for (int i = 0; i < 100000; i ++) {
        if (i % 2 == 0) {
            self.arr = @[@"1", @"2", @"3"];
        }
        else {
            self.arr = @[@"1"];
        }
        NSLog(@"Thread A: %@\n", self.arr);
    }
     
    //thread B
    for (int i = 0; i < 100000; i ++) {
        if (self.arr.count >= 2) {
            NSString* str = [self.arr objectAtIndex:1];
        }
        NSLog(@"Thread B: %@\n", self.arr);
    }
    

    同理,即使我们在访问objectAtIndex之前做了count的判断,线程B依旧很容易crash,原因也是由于前后两行代码之间arr所指向的内存区域被其他线程修改了。

    总结

    atomic的作用只是给getter和setter加了个锁,atomic只能保证代码进入getter或者setter方法内部时是安全的,一旦出了getter和setter,多线程安全只能靠程序员自己保障了。所以atomic属性和使用property的多线程安全并没什么直接的联系。
    atomic会带来一些性能损耗,所以一般用nonatomic,在需要做多线程安全的场景,自己去额外加锁做同步。

    线程安全的实现方法

    非原子性的:

    if (self.stringA.length >= 10) {
        NSString* subStr = [self.stringA substringWithRange:NSMakeRange(0, 10)];
    }
    

    加锁:

    //thread A
    [_lock lock];
    for (int i = 0; i < 100000; i ++) {
        if (i % 2 == 0) {
            self.stringA = @"a very long string";
        }
        else {
            self.stringA = @"string";
        }
        NSLog(@"Thread A: %@\n", self.stringA);
    }
    [_lock unlock];
     
    //thread B
    [_lock lock];
    if (self.stringA.length >= 10) {
        NSString* subStr = [self.stringA substringWithRange:NSMakeRange(0, 10)];
    }
    [_lock unlock];
    

    加锁以后认为是线程安全的。

    加锁方式:

    1. @synchronized(token)
    2. NSLock

    加锁和关锁要在同一个线程执行,要不会产生不可预知的问题。
    递归加锁不要用这个,因为调用这个lock 的方法两次在同一个线程里面会永久的锁住这个线程。
    递归用NSRecursiveLock 去实现递归加锁。

    1. dispatch_semapgore_t
    2. OSSpinLock

    性能损耗由上到下依次减小。

    相关文章

      网友评论

          本文标题:多线程的安全问题

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