This post first appeared on my personal blog

preface

Wikipedia describes the producer-consumer problem this way

The producer-consumer problem, also known as the Bounded buffer problem, is a classic case of multi-process synchronization. This problem describes what happens when two processes that share a fixed-size buffer — the so-called “producer” and “consumer” — actually run. The producer’s primary role is to generate a certain amount of data to put in the buffer and then repeat the process. At the same time, consumers consume this data in the buffer. The key to this problem is to ensure that producers do not add data when the buffer is full and consumers do not consume data when the buffer is empty.

To solve this problem, you have to let the producer sleep when the buffer is full (or give up data altogether) and wait until the next time the consumer consumes the data in the buffer to wake up and start adding data to the buffer. Similarly, you can put the consumer to sleep when the buffer is empty and then wake it up after the producer has added data to the buffer. Interprocess communication is usually used to solve this problem, and the commonly used methods include signal lamp method [1]. If the solution is not perfect, deadlock situations can easily occur. When a deadlock occurs, both threads fall asleep and wait for the other to wake them up. The problem can also be generalized to multiple producers and consumers.

scenario

In our company’s own project, there is a scene, which is IM message. When we receive the message, we do some business logic processing and database operation, and then refresh the list. The problem is that if messages are received very quickly, such as offline messages, there may be hundreds of messages pulled down. If each message is processed one by one, there will be two problems:

  • The last refresh is not complete, next time it will come in. The interface blinks
  • Each message is written to the database once, which takes time for I/O operations. As a result, performance problems are serious

The solution

The above problem can be solved by using producer-consumer

For simplicity and efficiency. Let’s use a timer to receive a message every 0.1 seconds and refresh the list, assuming it takes 2 seconds.

code

Define variables

@property (nonatomic,strong) NSMutableArray *array; @property (nonatomic,strong) dispatch_semaphore_t semaphore;Copy the code

Start timer

NSTimer * curTimer = [NSTimer timerWithTimeInterval: 0.1 target: self selector: @ the selector (producerFuncWithNumber:) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:curTimerforMode:NSDefaultRunLoopMode];
[curTimer fire];
Copy the code

Suppose a piece of data is received in 0.1 seconds

// (void)producerFuncWithNumber:(NSInteger)number{number = random()%10; Dispatch_queue_t t = dispatch_queue_create("222222", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(t, ^{
        dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
        
        [self.array addObject:[NSString stringWithFormat:@"%ld",(long)number]];
        NSLog(@"Produced %lu",(unsigned long)self.array.count);
        dispatch_semaphore_signal(self.semaphore);
        
    });
}
Copy the code

consumers

// consumer - (void)consumerFunc{dispatch_queue_t t1 = dispatch_queue_create("11111", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(t1, ^{
        
        while (YES) {
            if (self.array.count > 0) {
                dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
                NSLog(@"Consumed %lu",(unsigned long)self.array.count); [self.array removeAllObjects]; [self reload]; dispatch_semaphore_signal(self.semaphore); }}}); }Copy the code

Each time you refresh, let’s say it takes 2 seconds

-(void)reload{
    NSLog(@"Sleep for 2 seconds.");
    sleep(2);
}
Copy the code

The complete code is as follows

#import "ViewController.h"@interface ViewController () @property (nonatomic,strong) NSMutableArray *array; @property (nonatomic,strong) dispatch_semaphore_t semaphore; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; / / Do any additional setup after loading the view. / / open the timer NSTimer * curTimer = [NSTimer timerWithTimeInterval: 0.1 target:self selector:@selector(producerFuncWithNumber:) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:curTimerforMode:NSDefaultRunLoopMode];
    [curTimer fire];
    
    [self consumerFunc];
}

-(void)reload{
    NSLog(@"Sleep for 2 seconds.");
    sleep(2);
}
- (NSMutableArray *)array{
    if(! _array) { _array = [NSMutableArray array]; }return  _array;
}

- (dispatch_semaphore_t)semaphore{
    if(! _semaphore) { _semaphore = dispatch_semaphore_create(1); }return_semaphore; } // producerFuncWithNumber:(NSInteger)number{number = random()%10; Dispatch_queue_t t = dispatch_queue_create("222222", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(t, ^{
        dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
        
        [self.array addObject:[NSString stringWithFormat:@"%ld",(long)number]];
        NSLog(@"Produced %lu",(unsigned long)self.array.count); dispatch_semaphore_signal(self.semaphore); }); } // consumer - (void)consumerFunc{dispatch_queue_t t1 = dispatch_queue_create("11111", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(t1, ^{
        
        while (YES) {
            if (self.array.count > 0) {
                dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
                NSLog(@"Consumed %lu",(unsigned long)self.array.count); [self.array removeAllObjects]; [self reload]; dispatch_semaphore_signal(self.semaphore); }}}); } @endCopy the code

The output

The ios-producer consumer [5508:75404] produced one ios-producer consumer [5508:75407] produced two ios-producer consumers [5508:75406] produced three ios-producer consumers [5508:75411] produced four Ios-producer consumers [5508:75440] produced five ios-producer consumers [5508:75443] produced six ios-producer consumers [5508:75450] produced seven ios-producer consumers [5508:75458] produced eight Ios-producer consumers [5508:75463] produced 9 ios-producer consumers [5508:75472] produced 10 ios-producer consumers [5508:75480] produced 11 ios-producer consumers [5508:75481] produced 12 Ios-producer consumers [5508:75482] produced 13 ios-producer consumers [5508:75494] produced 14 ios-producer consumers [5508:75518] produced 15 ios-producer consumers [5508:75521] Sixteen ios-producer consumers [5508:75526] were produced 17 ios-producer consumers [5508:75528] 18 ios-producer consumers [5508:75531] were produced 19 Ios-producer consumer [5508:75545] produced 20 ios-producer consumer [5508:75405] consumed 20 ios-producer consumer [5508:75405] sleep for 2 seconds ios-producer consumer [5508:75545] produced 1 Ios-producer consumers [5508:75531] produce 2 ios-producer consumers [5508:75528] produce 3 ios-producer consumers [5508:75526] produce 4 ios-producer consumers [5508:75521] produce 5 Ios-producer consumers [5508:75518] produced 6 ios-producer consumers [5508:75494] produced 7 ios-producer consumers [5508:75482] produced 8 ios-producer consumers [5508:75481] produced 9 Ios-producer consumers [5508:75472] produced 10 ios-producer consumers [5508:75472] produced 11 ios-producer consumers [5508:75463] produced 12 ios-producer consumers [5508:75458] Thirteen ios-producer consumers [5508:75450] were produced fourteen ios-producer consumers [5508:75443] were produced fifteen ios-producer consumers [5508:75440] were produced sixteen Ios-producer consumers [5508:75406] produced 17 ios-producer consumers [5508:75407] produced 18 ios-producer consumers [5508:75404] produced 19 ios-producer consumers [5508:75411] Produced 20 ios-producer consumers [5508:75405] consumed 20 ios-producer consumers [5508:75405] sleep for 2 seconds ios-producer consumers [5508:75411] produced 1 ios-producer consumer [5508:75404] Produced 2...Copy the code

According to the output result, if it takes 2 seconds to complete the service logic each time, you can wait for the last time to complete and then fetch the data next time. At this point, there are already 20 pieces of data, which can be processed at one time, which is a big improvement in performance.

Pay attention to the point

The producer and the consumer are processing the semaphore respectively. In order to ensure the uniqueness of data, dispatch_semaphore_t semaphore is used to ensure that multiple threads are not crowded and do not grab data.

conclusion

The above is simple and practical for producers and consumers. When it is actually used, it can be flexible and practical, and sometimes it can have a large space for optimization.

The Demo address