Pack a garlic. Learn the next dispatch queue

Source: Internet
Author: User

Dispatch the true marrow of the queue: capable of serial, parallel, synchronous, asynchronous, and shared thread pooling.

Interface:

GCD is a C language-based apt. Although the GCD object has been turned into a Objective-c object in the latest system version, the API still maintains a pure C interface (with block extensions). This is good for implementing the underlying interface, and GCD provides an excellent and simple interface.

The Objective-c class name is Madispatchqueue and contains four calling methods:

1. Get the global Shared Queue method. GCD has multiple global queues with different priorities, and for the sake of simplicity, we keep one in the implementation.

2. Initialization functions for serial and parallel queues.

3. Asynchronous distribution calls

4. Synchronous distribution Calls

Interface declaration:

@interface Madispatchqueue:nsobject

+ (Madispatchqueue *) Globalqueue;

-(ID) initserial: (BOOL) serial;

-(void) Dispatchasync: (dispatch_block_t) block;

@end

The next goal is to implement the functionality of these methods.

Thread Pool Interface:

The thread pool interface behind the queue is simpler. It will actually perform the submitted task. The queue is responsible for submitting the queued task to it at the appropriate time.

The thread pool does only one thing: Post the task and run it. Corresponding, an interface has only one method:

@interface Mathreadpool:nsobject

-(void) Addblock: (dispatch_block_t) block;

@end

Since this is the core, let's implement it first.

Thread Pool Implementation

First look at the instance variables. Thread pooling can be accessed by multiple internal threads or by multiple external threads, and therefore threads are required to be secure. And where possible, GCD would use atomic manipulation, and I'm here in a way that was more popular before-locking. I need to know that the lock is in wait and lock-related signals, not just forcing it to be mutually exclusive, so I use nscondition instead of Nslock. If you are unfamiliar, nscondition is essentially a lock, just adding a condition variable:

Nscondition *_lock;

To know when to add a worker thread, I want to know the number of threads constructor, how many threads are being consumed, and the maximum number of threads that can be owned:

Nsuinteger _threadcount;

Nsuinteger _activethreadcount;

Nsuinteger _threadcountlimit;

Finally, a block list of nsmutablearray types is necessary to simulate a queue, add blocks from the queue backend, and delete from the front of the queue:

Nsmutablearray *_blocks;

The initialization function is simple. Initialize the lock and block array, arbitrarily set a maximum number of threads such as 128:

-(ID) init{

if (self = [super init])) {

_lock = [Nscondition alloc] init];

_blocks = [Nsmutablearray alloc] init];

_threadcountlimit = 128;

}

return self;

}

The worker thread runs a simple infinite loop. As long as the block array is empty, it waits. Once a block is added, it will be removed from the array and executed. At the same time, the number of active threads plus 1, the number of active threads minus 1 after completion;

- (void) Worderthreadloop: (ID) ignore{//to get the lock first, note the need to get it before the loop starts. As for the reason, you will understand when the cycle is over. [_lock Lock]; //Infinite loop Start     while(1) {         while([_blocks count] = =0{[_lock wait]; }        /*Note: This is the end of the inner loop rather than the IF judgment. The reason is due to false wakeup.         In simple terms, wait is also possible to return without a signal notification, so the correct way to condition detection so far is to re-perform conditional detection when wait returns. */        //Once there is a block in the queue, remove:dispatch_block_t block =[_blocks Firstobject]; [_blocks Removeobjectatindex:0]; //The active thread Count Plus, which indicates that there is a new line is impersonating in the processing task:_activethreadcount++; //now execute block, we have to release the lock, or the code will be executed concurrently when the deadlock occurs:[_lock unlock]; //after a secure release lock, execute blockblock (); //The block executes and the active thread count is reduced by 1. The operation must be done inside the lock to avoid race conditions, and finally the loop ends:[_lockLock]; _activethreadcount--; }}
//here is Addblock:- (void) Addblock: (dispatch_block_t) block{//the only thing to do here is to get the lock:[_lockLock]; //Add a new block to the block queue[_blocks Addobject:block]; //If there is an idle worker to execute this bock, there is nothing to do here. If there are not enough worker threads to handle the waiting block and the number of worker threads is not overrun, then we need to create a new thread:Nsuinteger idlethreads = _threadcount =_activethreadcount; if([_blocks Count] > idlethreads && _threadcount <_threadcountlimit)        {[Nsthread detachnewthreadselector: @selector (workerthreadloop:) totarget:self Withobject:nil]; _threadcount++; }//everything is ready because the idle thread is sleeping and waking it up:[_lock signal];//Last release Lock:[_lock unlock];//The thread pool can create a worker thread to handle the corresponding block before reaching the preset maximum number of threads. The queue is now implemented on this basis.     /*The queue implementation, like the thread pool, uses locks to protect its contents.     And the thread pool is different, it does not need to wait for the lock, also does not need the signal to trigger, is simply mutually exclusive, therefore uses the Nslock; */Nslock*Lock;//like the thread pool, it has pending blocks in Nsmutablearray. Nsmutablearray *_pendingblocks;//Whether the flag is a serial or parallel queue;BOOL _serial;//if it is a serial queue, you also need to identify whether the thread is currently running:BOOL _serialrunning;//There is no need to pay attention to the same processing of the wireless path in the parallel queue. //The global queue is a global variable, and the shared thread pool is the same. They are all created in +initialize:    StaticMadispatchqueue *Gglobalqueue; StaticMathreadpool *Gthreadpool;}+ (void) initialize{if(self = = [Madispatchqueueclass]) {Gglobalqueue=[[Madispatchqueue alloc] initserial:no]; Gthreadpool=[[Mathreadpool alloc] init]; }}//since the +initialize has already been initialized, +globalqueue simply returns the variable. + (Madispatchqueue *) Globalqueue {returnGglobalqueue;}//The things we do here are the same as dispatch_once, but when implementing the GCD API, it's a bit deceiving to use the GCD API, even if the code is different. //Initialize a queue: Initialize lock and Pending Blocks, set the _serial variable:+ (Madispatchqueue *) Globalqueue {returnGglobalqueue;}//The things we do here are the same as dispatch_once, but when implementing the GCD API, it's a bit deceiving to use the GCD API, even if the code is different. //Initialize a queue: Initialize lock and Pending Blocks, set the _serial variable:- (ID) Initserial: (BOOL) Serial {if(self =[Super Init]) {_lock=[[Nslock alloc] init]; _pendingblocks=[[Nsmutablearray alloc] init]; _serial=serial; }    returnSelf ;}//before implementing the rest of the public API, we need to implement an underlying method to distribute a block to the thread, and then continue to invoke itself to handle the other block:- (void) Dispatchoneblock {//The entire lifecycle is done by running block on the thread pool and distributing the code as follows:[Gthreadpool Addblock: ^{//then take the first block in the queue, which obviously needs to be done inside the lock to avoid problems:[_lockLock]; dispatch_block_t Block=[_pendingblocks Firstobject]; [_pendingblocks Removeobjectatindex:0]; [_lock unlock];//taking the block and releasing the lock, the block can then safely execute in the background thread:block ();//if it is executed in parallel, there is no need to do anything more. In the case of serial execution, the following actions are also required:        if(_serial) {//The serial queue will accumulate other blocks, but cannot execute until the previous block is completed. When the block is complete, Dispatchoneblock will then see if any other blocks are added to the queue. If so, it calls itself to handle the next block. If none, the queue's running state is set to No:[_lockLock]; if([_pendingblocks Count] >0) {[Self dispatchoneblock]; } Else{_serialrunning=NO;        } [_lock unlock]; }    }];}//using the above method to achieve Dispatchasync: it is very easy. Add block to Pending block queue, set the state when appropriate and call Dispatchoneblock:- (void) Dispatchasync: (dispatch_block_t) block {[_lockLock]; [_pendingblocks Addobject:block];//If the serial queue is idle, set the queue status to run and call Dispatchoneblock for processing.     if(_serial &&!)_serialrunning) {_serialrunning=YES; [Self dispatchoneblock];//if the queue is parallel, call Dispatchoneblock directly. Because multiple blocks can be executed in parallel, this ensures that even if other blocks are running, the new block can be executed immediately. }Else if(!_serial)    {[Self dispatchoneblock]; }//If the serial queue is already running, no additional processing is required. Because a call to Dispatchoneblock after block execution is finished, the block added to the queue will eventually be called. Then release the Lock:[_lock unlock];}//for DISPATCHSYNC:GCD, it is more ingenious to execute the block directly on the calling thread to prevent other blocks from executing on the queue (in the case of a serial queue). Here we do not have to do such a clever deal, we are just dispatchasync: encapsulation, let it wait until block execution is complete. //It is handled using local nscondition, and a done variable is used to indicate when the block is complete:- (void) Dispatchsync: (dispatch_block_t) block {nscondition*condition =[[Nscondition alloc] init]; __block BOOL Done=NO;//The following is an asynchronous distribution block. Blocks inside the block, and then set the done value to send a signal to the condition[Self Dispatchasync: ^{block (); [ConditionLock]; Done=YES;        [Condition signal];    [Condition unlock]; }];//inside the calling thread, wait for the signal to be done, and then return[ConditionLock];  while(!Done )    {[Condition wait]; } [Condition unlock];}

Conclusion: The global thread pool can be implemented using block queues and intelligently generated threads. Using a shared global thread pool, you can build a dispatch queue that provides basic serial/parallel, synchronous/asynchronous functionality. This rebuilds a simple gcd, although it lacks many very good features and is less efficient. But this can give us a glimpse of the internal work process.

(has downloaded the relevant documents, Baidu Cloud disk).

Pack a garlic. Learn the next dispatch queue

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.