数组如何安全地从一个线程传递到另一个线程?

时间:2022-10-09 06:14:14

I'm working with some audio processing which is not done on the main thread. My background thread repeatedly generates arrays of floats that my main thread will want to use to display something to the user. Will this be thread safe or is it oversimplified? Since this is in an OpenGL loop, I want to avoid blocking either thread with atomic locking.

我正在处理一些音频处理,这些不是在主线程上完成的。我的后台线程不断地生成浮动数组,我的主线程将使用这些数组向用户显示一些内容。这是线程安全还是过度简化?由于这是在OpenGL循环中,所以我希望避免使用原子锁定来阻塞任何一个线程。

I'm flexible with the storage format. (C array, NSArray, etc. and double, CGFloat, NSNumber, etc.)

我对存储格式很灵活。(C阵列、NSArray等、double、CGFloat、NSNumber等)

Note that each time the second method is called, it may or may not have actual new data to process. It just wants whatever the latest is.

注意,每次调用第二个方法时,它可能要处理实际的新数据,也可能没有。它只想要最新的东西。

@interface

@property (nonatomic, strong) NSMutableArray *generatedNumbers;
@property (nonatomic, strong) NSArray *passedNumbers;
...

@end

@implementation

//This is called over and over repeatedly and an unpredictable rate
- (void) generateSomeNumbers{
    ...
    [self.generatedNumbers removeAllObjects];
    for (Something x in something){
        ...
        [self.generatedNumbers addObject:someNSNumberOrCGFloat];
    }
}

//This is called from the main thread (opengl CADisplayLink)
- (void) doStuffWithLatestGeneratedNumbers{
    self.passedNumbers = [NSArray arrayWithArray:self.generatedNumbers];
    [self doStuffWithNumbers:self.passedNumbers];
}

Or how about this?:

或者这个怎么样?:

@interface

@property (nonatomic, strong) NSMutableArray *generatedNumbers;
@property (nonatomic, strong) NSArray *passedNumbers;
...

@end

@implementation

//This is called over and over repeatedly and an unpredictable rate
- (void) generateSomeNumbers{
    ...
    [self.generatedNumbers removeAllObjects];
    for (Something x in something){
        ...
        [self.generatedNumbers addObject:someNSNumberOrCGFloat];
    }

    [self copyNumbers:self.generatedNumbers];
}

- (void)copyNumbers:(NSArray *)numbers
{
    @autoreleasepool
    {
        dispatch_async(dispatch_get_main_queue(), ^{
            self.passedNumbers = [NSArray arrayWithArray:self.generatedNumbers];
                });
    }
}

//This is called from the main thread (opengl CADisplayLink)
- (void) doStuffWithLatestGeneratedNumbers{

    [self doStuffWithNumbers:self.passedNumbers];
}

2 个解决方案

#1


1  

Since this is in an OpenGL loop, I want to avoid blocking either thread with atomic locking

由于这是在OpenGL循环中,所以我希望避免使用原子锁定来阻塞任何一个线程

This makes things much harder. You will need to take some kind of lock unless the operation to swap the array value is atomic (ie. a single CPU instruction).

这让事情变得更加困难。除非交换数组值的操作是原子的,否则您需要使用某种锁。一个CPU指令)。

If you're dealing with values that are larger than the native instruction size of the CPU, the operation to assign a replacement will not be atomic.

如果处理的值大于CPU的本机指令大小,则分配替换的操作将不是原子操作。

Assigning an NSMutableArray pointer is atomic, but unless you are happy to leak the array it's replacing, you'll need to release the existing array. The combination of these two operations is not atomic, so needs to be protected with a lock (which is what marking the property as atomic does).

分配一个NSMutableArray指针是原子的,但是除非您乐于泄漏它正在替换的数组,否则您将需要释放现有的数组。这两个操作的组合不是原子操作,因此需要使用锁进行保护(这是将属性标记为原子操作的原因)。

I'd recommend double-checking that you can't take a lock here. The lock in question is a spin-lock protecting two instructions, so it's going to be very cheap even when contended. If you're using Objective-C, or allocating any memory from the heap at all, you're probably already working at too high a level of abstraction to worry about this sort of detail (doing any of things already involves taking locks).

我建议你再检查一下,这里不能上锁。所讨论的锁是一个自旋锁,保护两个指令,所以即使是争用,它也会非常便宜。如果您正在使用Objective-C,或者从堆中分配任何内存,那么您可能已经在非常高的抽象级别上工作,不需要担心这种细节(做任何事情都已经涉及到获取锁)。

An example of a situation where taking locks, using Objective-C, or even allocating memory is to be avoided is in realtime auto processing. Since the duration of a lock is not completely predictable and realtime audio threads have extremely tight deadlines (tighter than an OpenGL loop --- sometimes less than 1ms, with any overrun causing audible glitches), they can't be used safely. The standard way of passing data between threads then is a circular buffer.

在实时自动处理中,可以避免使用锁、使用Objective-C甚至分配内存。由于锁的持续时间不是完全可以预测的,而且实时音频线程的截止期非常紧(比OpenGL循环更紧——有时小于1ms,任何溢出都会导致可听到的故障),因此不能安全地使用它们。在线程之间传递数据的标准方法是循环缓冲区。

#2


0  

This is how I would do it:

我是这样做的:

@property (strong) NSArray* generatedNumbers;

....

- (void) generateSomeNumbers{
    NSMutableArray* tempNumbers = [[NSMutableArray alloc] init];

    for (Something x in something){
        ...
        [tempNumbers addObject:someNSNumberOrCGFloat];
    }

    [self setGeneratedNumbers: tempNumbers];
}

//This is called from the main thread (opengl CADisplayLink)
- (void) doStuffWithLatestGeneratedNumbers{

    [self doStuffWithNumbers: [self generatedNumbers]];
}

The above assumes that generatedNumbers is not ever mutated after the assignment in generateNumbers and also we are using ARC.

上面假设在generateNumbers中赋值之后,生成数不会发生突变,我们也使用ARC。

Note that the property is atomic. I think this is necessary to stop a potential race condition where the main thread tries to get generatedNumbers but the array is deallocated by the other thread before it has a chance to do the retain.

注意属性是原子的。我认为有必要阻止一个潜在的竞态条件,在这个条件下主线程试图获得通用的数字,但是在它有机会执行保留之前,该数组是由另一个线程分配的。

#1


1  

Since this is in an OpenGL loop, I want to avoid blocking either thread with atomic locking

由于这是在OpenGL循环中,所以我希望避免使用原子锁定来阻塞任何一个线程

This makes things much harder. You will need to take some kind of lock unless the operation to swap the array value is atomic (ie. a single CPU instruction).

这让事情变得更加困难。除非交换数组值的操作是原子的,否则您需要使用某种锁。一个CPU指令)。

If you're dealing with values that are larger than the native instruction size of the CPU, the operation to assign a replacement will not be atomic.

如果处理的值大于CPU的本机指令大小,则分配替换的操作将不是原子操作。

Assigning an NSMutableArray pointer is atomic, but unless you are happy to leak the array it's replacing, you'll need to release the existing array. The combination of these two operations is not atomic, so needs to be protected with a lock (which is what marking the property as atomic does).

分配一个NSMutableArray指针是原子的,但是除非您乐于泄漏它正在替换的数组,否则您将需要释放现有的数组。这两个操作的组合不是原子操作,因此需要使用锁进行保护(这是将属性标记为原子操作的原因)。

I'd recommend double-checking that you can't take a lock here. The lock in question is a spin-lock protecting two instructions, so it's going to be very cheap even when contended. If you're using Objective-C, or allocating any memory from the heap at all, you're probably already working at too high a level of abstraction to worry about this sort of detail (doing any of things already involves taking locks).

我建议你再检查一下,这里不能上锁。所讨论的锁是一个自旋锁,保护两个指令,所以即使是争用,它也会非常便宜。如果您正在使用Objective-C,或者从堆中分配任何内存,那么您可能已经在非常高的抽象级别上工作,不需要担心这种细节(做任何事情都已经涉及到获取锁)。

An example of a situation where taking locks, using Objective-C, or even allocating memory is to be avoided is in realtime auto processing. Since the duration of a lock is not completely predictable and realtime audio threads have extremely tight deadlines (tighter than an OpenGL loop --- sometimes less than 1ms, with any overrun causing audible glitches), they can't be used safely. The standard way of passing data between threads then is a circular buffer.

在实时自动处理中,可以避免使用锁、使用Objective-C甚至分配内存。由于锁的持续时间不是完全可以预测的,而且实时音频线程的截止期非常紧(比OpenGL循环更紧——有时小于1ms,任何溢出都会导致可听到的故障),因此不能安全地使用它们。在线程之间传递数据的标准方法是循环缓冲区。

#2


0  

This is how I would do it:

我是这样做的:

@property (strong) NSArray* generatedNumbers;

....

- (void) generateSomeNumbers{
    NSMutableArray* tempNumbers = [[NSMutableArray alloc] init];

    for (Something x in something){
        ...
        [tempNumbers addObject:someNSNumberOrCGFloat];
    }

    [self setGeneratedNumbers: tempNumbers];
}

//This is called from the main thread (opengl CADisplayLink)
- (void) doStuffWithLatestGeneratedNumbers{

    [self doStuffWithNumbers: [self generatedNumbers]];
}

The above assumes that generatedNumbers is not ever mutated after the assignment in generateNumbers and also we are using ARC.

上面假设在generateNumbers中赋值之后,生成数不会发生突变,我们也使用ARC。

Note that the property is atomic. I think this is necessary to stop a potential race condition where the main thread tries to get generatedNumbers but the array is deallocated by the other thread before it has a chance to do the retain.

注意属性是原子的。我认为有必要阻止一个潜在的竞态条件,在这个条件下主线程试图获得通用的数字,但是在它有机会执行保留之前,该数组是由另一个线程分配的。