同步此多线程代码(iOS/Cocoa)的正确方法是什么?

问题描述:

比方说,我有对象的NSMutableArrayNSMutableArray线程安全的),我有一个包含此数组的对象上,这些方法(这是为了清楚起见简化的例子):同步此多线程代码(iOS/Cocoa)的正确方法是什么?

- (void)addObject:(id)object { 
    if (_objectsArray == nil) { 
     _objectsArray = [NSMutableArray array]; 
    } 

    [_objectsArray addObject:object]; 

    if (_thread == nil) { 
     _thread = [[NSThread alloc] initWithTarget:self selector:@selector(__threadEntry:) object:nil]; 
     _thread.name = @"com.company.ThreadName"; 
     [_thread start]; 
    } 
} 

- (void)removeObject:(id)object { 
    [_objectsArray removeObject:object]; 

    if (_objectsArray.count == 0) { 
     _isRunning = NO; 
    } 
} 

- (void)stopRendering { 
    _isRunning = NO; 
} 

- (void)__threadEntry:(id)sender { 
    // Set up CADisplayLink on current run loop. 

    // "_isRunning" is declared as a "volatile BOOL" 
    _isRunning = YES; 
    while (_isRendering) { 
     [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 
    } 

    // Thread cleanup. 
} 

- (void)__threadProc { 
    @autoreleasepool { 
     [_objectsArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 
      // Do work 
     }]; 
    } 
} 

所以基本上,我有添加/从可变数组中删除对象的方法,但对数组中的对象的工作是在不同的线程上执行。即addObjectremoveObject都只从主线程调用,而工作(在__threadProc)是在不同的线程上完成的。

事实上,这段代码并不是线程安全的,因为在__threadProc中进行枚举时可以添加/删除对象。那么同步这个的正确方法是什么?

我不确定这里的锁是否是正确的答案,因为锁是否可以跨不同的方法工作?例如,如果我把一个锁定/解锁周围[_objectsArray addObject:object]addObject方法和锁定/解锁周围的工作在__threadProc,将在该工作(假设当然,这两者是相同的锁定对象(例如NSLock)的?

此外,添加/删除对象相比,多长时间的工作是在做__threadProc很少发生

+0

每次当您想要添加或删除对象时,从_objectsArray创建一个新数组。之后,在新阵列上添加或删除对象。完成后,将新数组分配给_objectsArray并确实需要。你试过了吗? – trungduc

+0

不,我没有尝试过,但那不起作用。如果新数组在__threadProc中枚举时被分配给addObject中的_objectsArray会发生什么?这也必须同步,这是我想在这里解决的问题。 – Chris

+0

在运行'enumerateObjectsUsingBlock'之前,从'_objectsArray'创建另一个数组,并使用它来代替'_objectsArray'和'enumerateObjectsUsingBlock' – trungduc

假设我们正在实施的Objective-C的线程安全的队列我们可以象这样开始:。

@implementation ThreadSafeQueue 
{ 
    NSMutableArray * _objectsArray; 
    NSLock *_lock; 
} 

- (instancetype)init 
{ 
    self = [super init]; 
    if (self) { 
    _objectsArray = [NSMutableArray array]; 
    _lock = [[NSLock alloc] init]; 
} 
    return self; 
} 

- (void)push:(id)object 
{ 
    [_lock lock]; 
    [_objectsArray addObject:object]; 
    [_lock unlock]; 
} 

// Or using the @synchronized construct: 

@synchronized (self) { 
    [_elements addObject:element]; 
    } 
@end 

他在ThreadSafeQueue类上面h作为初始化两个ivars的init方法:一个_objectsArray数组和一个NSLock。它有一个push方法:获取锁,将一个_object插入到数组中,然后释放该锁。许多线程可以同时调用push:但是[_objectsArray addObject:object]行一次只能在一个线程上运行。这些步骤可能会去是这样的:

Thread A calls the push: method 
Thread B calls the push: method 
Thread B calls [_lock lock] - since nobody else is holding the lock, 
Thread B acquires the lock 
Thread A calls [_lock lock] but the lock is held by Thread B so the method call doesn’t return - this pauses execution in thread A 
Thread B adds its objects to _objectsArray and calls [_lock unlock]. When this happens, Thread A’s [_lock lock] method returns and it goes on to insert its own object 

我们可以实现这个更简洁地使用@synchronized结构:

synchronized块具有与[_lock锁]相同的效果,[_lock解锁]中上面的例子。你可以把它看作锁定自己,就像自己是一个NSLock一样。开始{运行后的任何代码,并且在关闭之后的任何代码运行之前释放锁定之前,锁定是可用的。这真的很方便,因为这意味着你永远不会忘记打电话解锁!

//或使用@Synchronized结构:

@synchronized (self) { 
    [_elements addObject:element]; 
    } 

您可以在任何Objective-C的对象@synchronize。所以我们可以在上面的例子中使用@synchronized(_elements)而不是@synchronized(self),效果也是一样的。