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