17, iOS underlying analysis - multithreading (a) concepts

First, the processes and threads

1.1 process
      process is the basic unit of resource allocation of the operating system and scheduling. On iOS, instance of a running APP is a process, each process has its own separate memory addresses (private and protected memory space), and has all the resources needed to run independently.

Currently iOS is a single process, that process can be understood as the current application system is running.


1.2 thread
      thread is the basic unit process execution, process all tasks are executed in the thread, therefore, a process must have at least one thread. will default on a main thread, the UI thread after iOS program will start. UI must be operated in the main thread, whether the person may be some unknown problems.


1.3 Relationship processes and threads of
    a process can have multiple threads, but must have at least one thread.
    Address space: the same process address space can be shared by multiple threads of this process, but the address space between a process and the process is independent.
    Resources have: the same process resources can be shared by all threads in this process, such as memory, I / O, CPU, etc., but resources between a process and the process is independent of each other.
    After a process of any one thread collapse, it will lead to the collapse of the whole process, but the process crashes will not affect another process

Execution: with an inlet of each separate process running a program, and sequential execution sequence of program entry. However, the thread can not be executed independently be present depending on the application, there is provided a plurality of threads by the application program execution control
    thread is a basic unit of processor scheduling process is not.
    Process can be seen as a container threads, each process has run a program entry, but the thread can not run independently, must depend on the process
 
1.4 thread RunLoop relationship
    thread RunLoop is one to one, a RunLoop corresponds to a core thread. Why is the core, because RunLoop can be nested, but only a core of their stored corresponding relationship between a global dictionary
    RunLoop is to manage the thread, the thread will go to sleep when executing the task, when there are tasks to be wakes start the task (event-driven)
    runLoop is created when first acquired, is destroyed at the end of the thread
    main thread runLoop when the program starts it created a default
    when runLoop child thread is lazy loaded only when using It was only created in the child thread and therefore should pay attention to when using NSTimer ensure child threads RunLoop has been created, whether the person NSTimer not take effect.


 Second, multi-threaded

2.1 The concept and principles of
     a process can be complicated by multiple threads simultaneously perform their tasks, called multi-threading. Sharing operating system CPU time will be divided into essentially the same length of time interval, called "time slice" within a time slice, CPU can only handle one thread of tasks for a single-core CPU, in different time slice to perform tasks in different threads, to form a "false impression" to perform multiple tasks at the same time of:
    (the same time, CPU can only handle one thread, only one thread in a multithreaded concurrent execution, in fact. CPU scheduling is fast (switching) between multiple threads. If the thread scheduling CPU time is fast enough, causing the multithreaded "illusion" concurrent execution)

    the figure above, the CPU starts executing the thread time slice t3 3 the task, but the task has not been executed, came to t4, begin implementation of the mandate of the thread 4, in this time t4 finished piece on the implementation of task threads 4, to perform tasks when t5 then thread 3.
    Now many are multi-core CPU, each core can handle the task alone, to achieve "real" multi-threaded, but a APP easily dozens of concurrent threads, each core of any course in order to achieve the above-mentioned principles of multi-threading.
    Multithreading advantages and disadvantages:
   Advantages:
    can properly improve the efficiency of the program
    can be an appropriate increase in resource utilization (CPU, memory utilization)
    Cons:
    open thread needs to take up some memory space (by default, the main thread occupy 1M, the child thread occupies 512KB), if a large number of open threads, will take up a lot of memory space, reduce the level of performance.
    More threads, thread scheduling the CPU overhead on the greater
 
2.2, tasks, queue
    1, GCD There are two ways to perform tasks performed synchronously (Sync) ,Asynchronous execution (async)
    synchronization (sync): sync add tasks to the list, before the end of the task execution added, will have to wait to know before proceeding inside the queue after the task is completed. Will block threads can only perform tasks in the current thread (the current thread is not necessarily the main line), does not have the ability to open a new thread.
    Asynchronous (async): thread returns immediately, without waiting for it will continue to perform the following tasks, do not block the current thread can perform tasks in the new thread, with the ability (not necessarily start a new thread) to open a new thread. If it is not added to the main queue, asynchronous tasks will be executed in the sub-thread.
 
    Queue
    to store task execution queue. Linear queue is a special table, only the principle of using FIFO (First In First Out), i.e., a new task is always inserted at the end of the queue, and the read task time is always read from the head of the queue. Each read a task, a task released from the queue
    , there are two in the queue in GCD: serial queue and concurrent queue.
 
 2.3, iOS in several multi-threading
    in iOS, there are several ways to use the following multi-threaded.
    1, Pthread

POSIX thread, abbreviation Pthread, is the thread of the POSIX standard, is a common set of multi-threaded API, you can use the Unix / Linux / Windows cross-platform and other platforms. iOS is not used in Basic.
    2, NSTread

Apple package thread object-oriented class, you can directly manipulate threads than GCD, NSThread higher efficiency, created by the programmers themselves, after completion of task execution thread, the thread will automatically exit, the programmer can manually manage threads life cycle. Less frequently used.

NSTread alone will later re-analysis, analysis will be attached complete connection address.
   3, GCD

Full Grand Central Dispatch, implemented by the C language, is the apple of multi-core parallel computing solutions kicked out, GCD will automatically use more CPU cores to automatically manage the life cycle of the thread. Programmers only need to tell GCD tasks need to be performed, without having to write any code to manage threads f. GCD is the most frequently used iOS multi-threading technology.

GCD alone will later re-analysis, will be attached complete connection address.
    4, NSOperation

Multi-threaded object-oriented technology GCD package, often with the use NSOperationQueue high efficiency.

NSOperation later will separate further analysis, it will be attached complete connection address.

    //2: NSThread
    [NSThread detachNewThreadSelector:@selector(threadTest) toTarget:self withObject:nil];
    // 3: GCD
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self threadTest];
    });

    // 4: NSOperation
    [[[NSOperationQueue alloc] init] addOperationWithBlock:^{
        [self threadTest];
    }];


    //NSThread 的几种初始化方法
    NSThread * thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(threadDemo1:) object:@"thread1"];
    NSThread * thread2 = [[NSThread alloc] init];
    NSThread * thread3 = [[NSThread alloc] initWithBlock:^{
        NSLog(@"NSThread initWithBlock");
    }];


Third, the thread pool

    Thread Pool (Thread Pool)
    the name suggests is a multi-threaded u pond management lifecycle. iOS development does not directly come into contact with the thread pool, this is because GCD already includes management thread pool, we only need to perform tasks to obtain the thread by GCD.


    Thread of the life cycle of
    the life cycle of a thread include: Create - Ready - run - the death of the four stages. We can clog and exit to control the life cycle of the thread.

Thread scheduling when the thread pool will come first, to see whether the current thread is available. Into the thread pool
to determine the thread pool size is less than the core thread pool size (that is, to determine whether the thread pool is full), this place needs to say something. Has a maximum thread pool, there is a core thread pool size.
For example, the thread pool maximum is 100, the size of the core values of 80, the current thread pool size 60.60 <80 at this time will be to create a thread to execute

Saturation strategy :
 • AbortPolicy direct throw RejectedExecutionExeception exceptions to prevent normal
 • CallerRunsPolicy task will fall back to the caller
 • DisOldestPolicy lost waiting for the longest mission '
 • DisCardPolicy discards the task
 RejectedExecutionHandler four interfaces refused strategies are implemented.

 

Fourth, the security thread

Shared data in the program has multiple threads execute in parallel, thread-safe code by synchronization mechanisms to ensure that each thread can execute properly and correctly, there are no surprises data pollution.
 Multi-threaded operating results will not think of the shared data is thread-safe, otherwise, the thread is unsafe.
 However, when multiple threads access the same objects, how to ensure the card is thread-safe? This time we need to find a solution, then use the lock.
 For example:

 Analog ticketing system, two threads to access data, the middle is likely to get two threads access the same value has been operated, thus causing the data is not accurate. So it is necessary locking to ensure that when each thread is to get access to the right data.

@property (nonatomic, assign) NSInteger tickets;
@property (nonatomic, strong) NSMutableArray *mArray;

//线程安全
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.tickets = 20;
    // 资源问题
    // 数据库
    // 增删改查
    // 199 -- 100
//     1. 开启一条售票线程
        NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
        t1.name = @"售票 A";
        [t1 start];
    
        // 2. 再开启一条售票线程
        NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
        t2.name = @"售票 B";
        [t2 start];
}
- (void)saleTickets {
    
    // runloop & 线程 不是一一对应
    while (YES) {
        // 0. 模拟延时
        //NSObject *obj = [[NSObject alloc] init];
        //obj 是自己的临时对象,对其他访问该区域的无影响
        //可以锁self 那么访问该方法的时候所有的都锁住,可以根据需求特定锁
        @synchronized(self){
            // 递归 非递归
            [NSThread sleepForTimeInterval:1];
            // 1. 判断是否还有票
            if (self.tickets > 0) {
                // 2. 如果有票,卖一张,提示用户
                self.tickets--;
                NSLog(@"剩余票数 %zd %@", self.tickets, [NSThread currentThread]);
            } else {
                // 3. 如果没票,退出循环
                NSLog(@"没票了,来晚了 %@", [NSThread currentThread]);
                break;
            }
            //在锁里面操作其他的变量的影响
            [self.mArray addObject:[NSDate date]];
            NSLog(@"%@ *** %@",[NSThread currentThread],self.mArray);
        }
    }
}

@synchronized(){}

Mutex
    to ensure that within the lock code, the same time, only one thread can execute.
    Mutex lock range, should be as small as possible, lock the greater the range, efficiency worse.
 Mutex parameters
    can be locked in any NSObject objects
    Note: Be sure to lock object d ensure that all threads can access
    if the code is only one place need to lock, mostly with self, to avoid re-create a separate lock object

Properties of thread safety

@property (nonatomic, copy) NSString *name;

 atomic atomic properties, is ready for the development of multi-threaded, is the default property!
 atomic itself only in `setter` method properties, increasing the lock (spin lock), can guarantee the same time, only one thread attributes` `write operation
 at the same time single (thread) Write Once (thread) to read a thread processing
 nonatomic non atoms properties
 not locked! High performance!
 
 atomic thread-safe, you need to consume a lot of resources
 nonatomic: non-thread-safe, suitable for small mobile memory devices
 iOS developers recommend
 properties are declared as nonatomic try to
 avoid multi-threading to grab the same piece of resources
 as far as possible will be locked, resource grab business logic post to the server process, to reduce the pressure of the mobile client.

// 在 OC 中,如果同时重写 了 setter & getter 方法,系统不再提供 _成员变量,需要使用合成指令
// @synthesize name 取个别名:_name
@synthesize name = _name;
#pragma mark - 模拟原子属性示例代码
- (NSString *)name {
    return _name;
}
- (void)setName:(NSString *)name {
    //增加一把锁,就能够保证一条线程在同一时间写入!
    @synchronized (self) {
        _name = name;
    }
}

More analysis of the lock, and then later for analysis.

 

 

 

Fifth, the communication between threads

1, communication between several threads
    in the interview, the interview asked how often equipment is communication between threads, many people will answer in data acquisition sub-thread, switch back to the main thread to refresh the UI. So is there any other way? Apple's official documentation to give us lists several ways to communicate between threads of
 
the table on the map is in accordance with the technical complexity from low to high order, the latter two of which can only be used in OS X.
    1, Direct message: This is a very familiar -performSelector: Series
    2, Global variables ...: directly through global variables, shared memory, etc., but this approach will result in resource grab, comes to thread safety issues.
    3, Conditions: a particular lock - a lock condition, when a lock condition is a thread waiting (the wait) time, and the thread is blocked from going to sleep, a signal transmission condition for uniform lock on another thread ( single), the waiting thread will be woken continue to perform the task.
    4, Run loop sources: Custom Run loop sources realized by the back of the text will look RunLoop alone.
    5, Ports and sockets: to implement inter-thread communication through the port and socket.
 
 2, an example of inter-thread communication

-performSelector: Series

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//        [self performSelector:@selector(ceshi1)];
//        2、使用了NSTimer 实现2秒后调用,NSTimer需要启用RunLoop
//        [self performSelector:@selector(ceshi2:) withObject:@"测试" afterDelay:2];
//        [[NSRunLoop currentRunLoop] run];
//        3、
//        [self performSelector:@selector(ceshi3:ob:) withObject:@"测试" withObject:@"多线程"];
        /**
            回到主线程
            waitUntilDone:
            YES:必须执行完主线程才能往下走   先执行 ceshi2: 再执行 ”走不走“
            NO:不需要执行完主线程,可以先往下走.   先执行 ”走不走“ 再执行ceshi2:
         其实当waitUntilDone:YES ,是用到了 RunLoop的知识,让其一直在等待 直到完成,才往下走。
        */
        [self performSelectorOnMainThread:@selector(ceshi2:) withObject:@"测试123" waitUntilDone:YES];
        NSLog(@"走不走?");
    });
    
//    定义一个全局变量,通过加锁的方式在不同线程中访问,来实现线程通讯的目的
}
-(void)ceshi1
{
    NSLog(@"12345");
}
-(void)ceshi2:(id)objc
{
    NSLog(@"12345  %@",objc);
    dispatch_async(dispatch_get_main_queue(), ^{
        self.view.backgroundColor = [UIColor orangeColor];
    });
}
-(void)ceshi3:(id)objc ob:(id)ob
{
    NSLog(@"12345  %@  %@",objc ,ob);
}

 

NSPort thread communication

In fact, connected through communication port number, faster. Code have made the comment, not much introduction directly on the code

#import "PortViewController.h"
#import <objc/runtime.h>
#import "KCPerson.h"

@interface PortViewController ()<NSMachPortDelegate>
@property (nonatomic, strong) NSPort *myPort;
@property (nonatomic, strong) KCPerson *person;

@end

@implementation PortViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"Port线程通讯";
    self.view.backgroundColor = [UIColor whiteColor];

//    依赖于端口通讯,更加直接
    //1. 创建主线程的port
    // 子线程通过此端口发送消息给主线程
    self.myPort = [NSMachPort port];
    //2. 设置port的代理回调对象
    self.myPort.delegate = self;
    //3. 把port加入runloop,接收port消息。在主线程中,这个地方不能再RunLoop run
//   * self.myPort 是当前收发消息的时候,需要保持其生命周期,所以要加入运行循环。
    [[NSRunLoop currentRunLoop] addPort:self.myPort forMode:NSDefaultRunLoopMode];
    
    self.person = [[KCPerson alloc] init];
    [NSThread detachNewThreadSelector:@selector(personLaunchThreadWithPort:)
                             toTarget:self.person
                           withObject:self.myPort];
    
}

#pragma mark - NSMachPortDelegate

- (void)handlePortMessage:(NSPortMessage *)message{
    
    NSLog(@"VC == %@",[NSThread currentThread]);
    
    NSLog(@"从person 传过来一些信息:");
//    NSLog(@"localPort == %@",[message valueForKey:@"localPort"]);
//    NSLog(@"remotePort == %@",[message valueForKey:@"remotePort"]);
//    NSLog(@"receivePort == %@",[message valueForKey:@"receivePort"]);
//    NSLog(@"sendPort == %@",[message valueForKey:@"sendPort"]);
//    NSLog(@"msgid == %@",[message valueForKey:@"msgid"]);
//    NSLog(@"components == %@",[message valueForKey:@"components"]);
    //会报错,没有这个隐藏属性
    //NSLog(@"from == %@",[message valueForKey:@"from"]);
    
    NSArray *messageArr = [message valueForKey:@"components"];
    NSString *dataStr   = [[NSString alloc] initWithData:messageArr.firstObject  encoding:NSUTF8StringEncoding];
    NSLog(@"传过来一些信息 :%@",dataStr);
    NSPort  *destinPort = [message valueForKey:@"remotePort"];
    
    if(!destinPort || ![destinPort isKindOfClass:[NSPort class]]){
        NSLog(@"传过来的数据有误");
        return;
    }
    
    NSData *data = [@"VC收到!!!" dataUsingEncoding:NSUTF8StringEncoding];
    
    NSMutableArray *array  =[[NSMutableArray alloc]initWithArray:@[data,self.myPort]];
    
    // * 非常重要,如果你想在Person的port接受信息,必须加入到当前主线程的runloop。 destinPort 是临时变量所以需要加入运行循环
    [[NSRunLoop currentRunLoop] addPort:destinPort forMode:NSDefaultRunLoopMode];
    
    NSLog(@"VC == %@",[NSThread currentThread]);
//    又发回去数据
    BOOL success = [destinPort sendBeforeDate:[NSDate date]
                                        msgid:10010
                                   components:array
                                         from:self.myPort
                                     reserved:0];
    NSLog(@"%d",success);
}

- (void)getAllProperties:(id)somebody{

    u_int count = 0;
    objc_property_t *properties = class_copyPropertyList([somebody class], &count);
    for (int i = 0; i < count; i++) {
        const char *propertyName = property_getName(properties[i]);
         NSLog(@"%@",[NSString stringWithUTF8String:propertyName]);
    }
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
@end
//KCPerson.h
#import <Foundation/Foundation.h>

@interface KCPerson : NSObject
- (void)personLaunchThreadWithPort:(NSPort *)port;
@end


//  KCPerson.m
#import "KCPerson.h"

@interface KCPerson()<NSMachPortDelegate>
@property (nonatomic, strong) NSPort *vcPort;
@property (nonatomic, strong) NSPort *myPort;
@end

@implementation KCPerson

- (void)personLaunchThreadWithPort:(NSPort *)port{
 
    NSLog(@"VC 响应了Person里面");
    @autoreleasepool {
        //*1. 保存主线程传入的port (端口)
        self.vcPort = port;
        //2. 设置子线程名字
        [[NSThread currentThread] setName:@"KCPersonThread"];
        //3. 开启runloop 端口的运行基于RunLoop
        [[NSRunLoop currentRunLoop] run];
        //4. 创建自己port
        self.myPort = [NSMachPort port];
        //5. 设置port的代理回调对象
        self.myPort.delegate = self;
        //6. 完成向主线程port发送消息
        [self sendPortMessage];
    }
}


/**
 *   完成向主线程发送port消息
 */

- (void)sendPortMessage {
 
    NSData *data1 = [@"Gavin" dataUsingEncoding:NSUTF8StringEncoding];
    NSData *data2 = [@"Cooci" dataUsingEncoding:NSUTF8StringEncoding];

    NSMutableArray *array  =[[NSMutableArray alloc]initWithArray:@[data1,self.myPort]];
    // 发送消息到VC的主线程
    // 第一个参数:发送时间。
    // msgid 消息标识。
    // components,发送消息附带参数。
    // reserved:为头部预留的字节数
    [self.vcPort sendBeforeDate:[NSDate date]
                          msgid:10086
                     components:array
                           from:self.myPort
                       reserved:0];
    
}

#pragma mark - NSMachPortDelegate

- (void)handlePortMessage:(NSPortMessage *)message{
    
    NSLog(@"person:handlePortMessage  == %@",[NSThread currentThread]);

    NSLog(@"从VC 传过来一些信息:");
    NSLog(@"components == %@",[message valueForKey:@"components"]);
    NSLog(@"receivePort == %@",[message valueForKey:@"receivePort"]);
    NSLog(@"sendPort == %@",[message valueForKey:@"sendPort"]);
    NSLog(@"msgid == %@",[message valueForKey:@"msgid"]);
}


@end

The results output. Between the VC and the process is the person identified communication connection, the thread of the call through the port person, person and then transmit a message through a port to the VC, VC respective callbacks and then modify the data passed to the person.

2020-03-12 22:53:18.165625+0800 004---线程通讯[47226:829715] VC 响应了Person里面
2020-03-12 22:53:18.216873+0800 004---线程通讯[47226:827356] VC == <NSThread: 0x600001ea23c0>{number = 1, name = main}
2020-03-12 22:53:18.217275+0800 004---线程通讯[47226:827356] 从person 传过来一些信息:
2020-03-12 22:53:18.217534+0800 004---线程通讯[47226:827356] 传过来一些信息 :Gavin
2020-03-12 22:53:18.220513+0800 004---线程通讯[47226:827356] VC == <NSThread: 0x600001ea23c0>{number = 1, name = main}
2020-03-12 22:53:18.220733+0800 004---线程通讯[47226:827356] 1
2020-03-12 22:53:18.221277+0800 004---线程通讯[47226:827356] person:handlePortMessage  == <NSThread: 0x600001ea23c0>{number = 1, name = main}
2020-03-12 22:53:18.221480+0800 004---线程通讯[47226:827356] 从VC 传过来一些信息:
2020-03-12 22:53:18.221783+0800 004---线程通讯[47226:827356] components == (
    <5643e694 b6e588b0 212121>,
    "<NSMachPort: 0x600003ca08f0>"
)
2020-03-12 22:53:18.221993+0800 004---线程通讯[47226:827356] receivePort == <NSMachPort: 0x600003cac580>
2020-03-12 22:53:18.222145+0800 004---线程通讯[47226:827356] sendPort == <NSMachPort: 0x600003ca08f0>
2020-03-12 22:53:18.222303+0800 004---线程通讯[47226:827356] msgid == 10010

 

Published 83 original articles · won praise 12 · views 180 000 +

Guess you like

Origin blog.csdn.net/shengdaVolleyball/article/details/104808306