Reputation: 2059
trying to make thread safe array but it works not as I expected
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
public class SafeArray<Element> {
private var array = [Element]()
private let queue = DispatchQueue(label: "queueBarrier", attributes: .concurrent)
public func append(element: Element) {
queue.async(flags: .barrier) {
self.array.append(element)
}
}
public var elements: [Element] {
var result = [Element]()
queue.sync {
result = self.array
}
return result
}
public var last: Element? {
var result: Element?
queue.sync {
result = self.array.last
}
return result
}
}
var safeArray = SafeArray<Int>()
var array = Array<Int>()
DispatchQueue.concurrentPerform(iterations: 10) { (int) in
let last = array.last ?? 0
array.append(last + 1)
print("array = [..\(last)]")
}
print(array)
DispatchQueue.concurrentPerform(iterations: 10) { (int) in
let last = safeArray.last ?? 0
safeArray.append(element: last + 1)
print("safeArray = [..\(last)]")
}
print(safeArray.elements)
I expected that array should have some mess but safeArray should have numbers from 0 to 9.
I understand that array have 3 values but safeArray has 10 values as expected. But why this values not from 0 to 9?
Thank you!
Upvotes: 3
Views: 4629
Reputation: 13980
Remember that while barrier makes operation itself thread-safe, the order between 2 operations is undefined. So if you want to depend on value of self.array.last
, you have to get its value only after you were able to get through the barrier. I.e. you can have a function like:
public func appendAfterOperationOnLast(_ operation: @escaping (Element?) -> Element) {
queue.async(flags: .barrier) {
let last = self.array.last // <- get the value here
let element = operation(last)
self.array.append(element)
}
}
And then
DispatchQueue.concurrentPerform(iterations: 10) { i in
safeArray.appendAfterOperationOnLast { last in
return (last ?? 0) + 1
}
}
print(safeArray.elements)
prints the expected result:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Upvotes: 0
Reputation: 1
I have created a thread-safe NSMutableArray and its working as expected.
//
// GCDTSNSMutableArray.m
// GCD
//
// Created by Vikas Kumar Jangir on 07/05/19.
//
//
#import "GCDTSNSMutableArray.h"
@interface GCDTSNSMutableArray()
@property (nonatomic,strong) NSMutableArray *internalArray;
@property (nonatomic) dispatch_queue_t queue;
@property (nonatomic, strong) NSString *queueName;
@end
@implementation GCDTSNSMutableArray
- (instancetype)init {
self = [super init];
if (self) {
self.internalArray = [NSMutableArray new];
//Make unique queue for every new insatance.
self.queueName = [NSString stringWithFormat:@"GCDTSNSMutableArray_%@",[GCDCommonUtil generateUUID]];
self.queue = dispatch_queue_create([self.queueName cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);
}
return self;
}
#pragma mark - Add operations
- (void)addObject:(id)object {
// Check for valid input object
if (object == nil) {
NSLog(@"Object must be nonnull");
return;
}
// Check for valid input index
dispatch_sync(self.queue, ^{
[self.internalArray addObject:object];
});
}
- (void)insertObject:(id)object atIndex:(NSUInteger)index {
// Check for valid input object
if (object == nil) {
NSLog(@"Object must be nonnull");
return;
}
// Check for valid input index
NSUInteger numberOfElements = [self count];
if (index > numberOfElements) {
NSLog(@"Index %lu is out of range [0..%lu]",(unsigned long)index,(unsigned long)numberOfElements);
return;
}
dispatch_sync(self.queue, ^{
[self.internalArray insertObject:object atIndex:index];
});
}
- (void)addObjectsFromArray:(NSArray *)array {
// Valid input array
if (array == nil) {
NSLog(@"Array must be nonnull");
return;
}
if ([array count] == 0) {
NSLog(@"Array must be not empty");
return;
}
// Add objects from array
dispatch_sync(self.queue, ^{
[self.internalArray addObjectsFromArray:array];
});
}
#pragma mark - Remove Operation
- (void)removeObject:(NSObject *)object {
// Valid input object
if (object == nil) {
NSLog(@"Object must be nonnull");
return;
}
// Remove object from array
dispatch_sync(self.queue, ^{
[self.internalArray removeObject:object];
});
}
- (void)removeObjectAtIndex:(NSUInteger)index {
// Valid input index
NSUInteger numberOfElements = [self count];
if (index >= numberOfElements) {
NSLog(@"Index is out of range");
return;
}
// Remove object at index from array
dispatch_sync(self.queue, ^{
[self.internalArray removeObjectAtIndex:index];
});
}
- (void)removeLastObject {
dispatch_sync(self.queue, ^{
[self.internalArray removeLastObject];
});
}
- (void)removeAllObjects {
// Check nonempty array
NSUInteger numberOfElements = [self count];
if (numberOfElements == 0) {
NSLog(@"Array is empty");
return;
}
// Remove all objects from array
dispatch_sync(self.queue, ^{
[self.internalArray removeAllObjects];
});
}
#pragma mark - Count,Search,Copy
-(NSUInteger)count {
__block NSUInteger count = 0;
dispatch_sync(self.queue, ^{
count = [self.internalArray count];
});
return count;
}
- (id)copy {
__block id returnArray;
dispatch_sync(self.queue, ^{
returnArray = [self.internalArray copy];
});
return returnArray;
}
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject {
dispatch_sync(self.queue, ^{
[self.internalArray replaceObjectAtIndex:index
withObject:anObject];
});
}
- (id)objectAtIndex:(NSUInteger)index {
// Valid input index
NSUInteger numberOfElements = [self count];
if (index >= numberOfElements) {
NSLog(@"Index %lu is out of range [0..%lu]",(unsigned long)index,(unsigned long)numberOfElements);
return nil;
}
// Return object at index in array
id __block object;
dispatch_sync(self.queue, ^{
object = [self.internalArray objectAtIndex:index];
});
return object;
}
- (NSUInteger)indexOfObject: (NSObject *)object {
NSUInteger __block result;
dispatch_sync(self.queue, ^{
result = [self.internalArray indexOfObject:object];
});
return result;
}
- (BOOL)containsObject: (id)object {
BOOL __block result;
dispatch_sync(self.queue, ^{
result = [self.internalArray containsObject:object];
});
return result;
}
- (NSArray *)toNSArray {
NSArray __block *array;
dispatch_sync(self.queue, ^{
array = [[NSArray alloc] initWithArray:self.internalArray];
});
return array;
}
- (void)enumerateObjectsUsingBlockInSync:(BOOL)sync withBlock:(__attribute__((noescape)) void (^)(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop))block {
if (!sync) {
[self.internalArray enumerateObjectsUsingBlock:block];
} else {
dispatch_sync(self.queue, ^{
[self.internalArray enumerateObjectsUsingBlock:block];
});
}
}
- (void)executeOnSynchWithCompletionBlock:(GCDThreadSafeNSMutableArrayCompletionBlock)compBlock {
dispatch_sync(self.queue, compBlock);
}
@end
Upvotes: -2
Reputation: 689
The way barrier
works is by making sure the DispatchWorkItem
(the block for append(:_)
) is going wait until all other DispatchWorkItem
s are done before executing its perform
(the code inside the block).
Hence, a barrier
.
If you look closely, you got one (DispatchWorkItem
) inside your last
call.
Since you're calling last
the first thing you do concurrently in DispatchQueue.concurrentPerform
, you'll have a stack of DispatchWorkItem
s waiting in the queue.
This means all your append(_:)
calls will be waiting since they're flagged as barrier
and your last
calls will all execute first, thus getting a lot of zeroes until all the DispatchWorkItem
s for last
are done before squeezing in two appends(_:)
The way barrier
works within a concurrent queue, is that it will actually wait until all pending DispatchWorkItem
s are done, before starting, and nothing else will start concurrently alongside it until it's done.
It sorta "disables" the concurrent nature of the queue temporarily, unless I'm mistaken.
I'm generally hesitant to introduce locks or semaphore as suggested by others here and they can cause more issues unless you first understand how GCD works.
It looks like you're trying to solve two things, first having an append(_:)
that works concurrently and mutating an array in an parallel operation that depends on its current state. Try to break down what you're trying to solve first so someone can give you a better answer.
Upvotes: 8
Reputation: 3526
Why not use DispatchSemaphore
?
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
public class SafeArray<Element> {
private var array = [Element]()
private let semaphore = DispatchSemaphore(value: 1)
private var lastEl: Element?
public func append(element: Element) {
self.array.append(element)
}
public var elements: [Element] {
var result = [Element]()
result = self.array
return result
}
public var last: Element? {
self.semaphore.wait()
lastEl = self.array.last
self.semaphore.signal()
return lastEl
}
}
var safeArray = SafeArray<Int>()
var array = Array<Int>()
DispatchQueue.concurrentPerform(iterations: 10) { (int) in
let last = array.last ?? 0
array.append(last + 1)
print("array = [..\(last)]")
}
print(array)
DispatchQueue.concurrentPerform(iterations: 10) { (int) in
let last = safeArray.last ?? 0
safeArray.append(element: last + 1)
print("safeArray = [..\(last)]")
}
print(safeArray.elements)
Upvotes: 0