iOS - 多线程总结

一 多线程概念

进程:系统中正在运行的一个应用程序.一个app就是一个进程.像我们进入后台划走的操作就是 杀掉进程,每个进程都是独立的.
线程:一个进程可以开启多个线程,线程是可以并行执行不同任务的,多线程可以充分的利用多核cpu来处理任务

关系

1.同一个进程内的所有线程共享本进程的地址空间,共享本进程的资源,比如内存 cpu等,而进程之间则是独立的地址空间
2.一个进程奔溃后,在保护模式下不会对其他进程产生影响,而一个线程奔溃后会导致整个进程奔溃

线程的优缺点

1.能适当提高程序的执行效果
2.能适当提高资源的利用率
3.线程上的任务执行结束后,线程会自动销毁

缺点:
开启线程是需要占用一定的内存空间的,开启的线程越多,cpu在调用上开销就越大.而且程序设计更加的复杂.线程之间的通信,多线程数据的共享

多线程的实现方式
  thread : 可以直接操作线程对象,线程生命周期由程序员管理
 NSOperation 给予GCD,线程生命周期系统自动管理
 
 GCD: 一套改进的c语言多线程Api,能充分利用设备的多和优势
NSThread

我们先来看创建的方式

NSThread *thread1=[[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"线程一"];
    thread1.name =@"线程一";
    [thread1 start];

    //自动启动线程
    [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"线程2"];

    //隐式启动 后台异步执行
    [self performSelectorInBackground:@selector(run:) withObject:@"线程三"];

-(void)run:(NSString *)threadStr{
    
    /*
     isMainThread 判断是否为主线程
     currentThread  获取当前程序
    */
    if (![[NSThread currentThread] isMainThread]) {
        NSLog(@"这不是主线程");
    }
    
    if ([threadStr isEqualToString:@"线程一"]) {
        NSLog(@"thread = %@",[NSThread currentThread]);
    } else if ([threadStr isEqualToString:@"线程2"]) {
        NSLog(@"thread = %@",[NSThread currentThread]);
    } else{
        
        NSLog(@"thread = %@",[NSThread currentThread]);
    }
    [[NSThread currentThread] cancel];
}

其他相关的方法

- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
//指定某一个线程执行
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait
//主线程实现
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

下面来个具体的方法,子线程实现耗时操作,在执行完之后回到主线程

/模拟子线程耗时操作,完成之后回到主线程刷新UI
    NSThread *subthread=[[NSThread alloc]initWithTarget:self selector:@selector(runtestSub) object:@"子线程"];
    [subthread start];
    //延迟两秒执行
    [self performSelector:@selector(run:) withObject:@"线程4" afterDelay:2.0f];
    //指定subthread线程上执行
    [self performSelector:@selector(runtestSub) onThread:subthread withObject:nil waitUntilDone:NO];

-(void)runtestSub{
    
    NSLog(@"thread = %@",[NSThread currentThread]);
    sleep(4);
    
    [self performSelectorOnMainThread:@selector(mainUI) withObject:nil waitUntilDone:NO];
    NSLog(@"执行完主线程了");
    [[NSThread currentThread] cancel];
    
}
-(void)mainUI{
    NSLog(@"这是主线程吧  thread = %@",[NSThread currentThread]);
    sleep(3);
}

NSThread日常开发中常用的也就这些.下面重点来看一下GCD,这个在开发中用的可就太多了.

GCD

了解GCD,你必须先了解两个概念,队列和任务.
同步执行 :同步添加到当前的队列中,在添加的任务结束之前,会一直等待,只能在当前线程中执行任务,不具备开区新线程的能力
异步执行 :不会做任务等待,可以继续执行任务. 在新的线程中执行任务,具备开启新线程的能力
串行队列 :让任务一个接一个的执行
并发队列:可以让多个任务同事执行. 并发队列只有在异步(async)方法下才有效

3.经典各种组合模式
了解了队列任务的概念,就有各种组合.我们分别一个一个的来实现一下.
1.同步+串行:没有开启新线程,串行执行任务

//同步+串行
-(void)gcdtest{
    
    dispatch_queue_t queue = dispatch_queue_create("Serialqueue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        NSLog(@"thread线程是--%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"thread 2线程是--%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"thread 3线程是--%@",[NSThread currentThread]);
    });
}

结果:2022-05-10 10:11:52.919600+0800 多线程demo[10086:447048] thread线程是--<_NSMainThread: 0x60000067c8c0>{number = 1, name = main}
2022-05-10 10:11:52.919767+0800 多线程demo[10086:447048] thread 2线程是--<_NSMainThread: 0x60000067c8c0>{number = 1, name = main}
2022-05-10 10:11:52.919923+0800 多线程demo[10086:447048] thread 3线程是--<_NSMainThread: 0x60000067c8c0>{number = 1, name = main}

总结:可以看到是按顺序执行的,由于是同步任务,并没有开辟新的子线程,所以是在主线程上面执行的.
2.异步+串行:有开启新线程(1条),串行执行任务

-(void)gcdtest1{
    
    dispatch_queue_t seruqueue = dispatch_queue_create("serqueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(seruqueue, ^{
        NSLog(@"thread线程是--%@",[NSThread currentThread]);
    });
    dispatch_async(seruqueue, ^{
        NSLog(@"thread 2线程是--%@",[NSThread currentThread]);
    });
    dispatch_async(seruqueue, ^{
        NSLog(@"thread 3线程是--%@",[NSThread currentThread]);
    });
}
结果:2022-05-10 10:16:27.454716+0800 多线程demo[10139:450303] thread线程是--<NSThread: 0x600002f0c800>{number = 6, name = (null)}
2022-05-10 10:16:27.454834+0800 多线程demo[10139:450303] thread 2线程是--<NSThread: 0x600002f0c800>{number = 6, name = (null)}
2022-05-10 10:16:27.454923+0800 多线程demo[10139:450303] thread 3线程是--<NSThread: 0x600002f0c800>{number = 6, name = (null)}

开启了一条新线程,但由于是串行队列所以还是得一个一个的执行.
3 异步+并发:有开启新线程(多条),并发执行任务

-(void)gcdtest2{
    
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"thread线程是--%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"thread 2线程是--%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"thread 3线程是--%@",[NSThread currentThread]);
    });
}
打印结果:2022-05-10 10:23:03.398415+0800 多线程demo[10208:454142] thread线程是--<NSThread: 0x600001fec040>{number = 5, name = (null)}
2022-05-10 10:23:03.398415+0800 多线程demo[10208:454141] thread 2线程是--<NSThread: 0x600001fd8000>{number = 6, name = (null)}
2022-05-10 10:23:03.398415+0800 多线程demo[10208:454146] thread 3线程是--<NSThread: 0x600001ffe880>{number = 4, name = (null)}

当异步并发执行的时候,开启了多个子线程.并且执行的顺序是不固定的.
4 主线程+同步

-(void)gcdtest3{
    dispatch_queue_t mainqueue = dispatch_get_main_queue();
    dispatch_sync(mainqueue, ^{
        NSLog(@"奔溃了");
    });
}

总结:会发现,项目直接奔溃了.那么奔溃的原因是什么呢?这是由于,在主线程上,先执行viewdidload,然后执行gcdtest3中的事件.此时主线程是等待gcdtest3执行完的,而这个时候,我们把gcdtest3中的事件加到了主线程上,现在viewdidload和 NSLog(@"奔溃了");变成了同一级.gcdtest3中的方法一直没法执行,造成的结果就是viewdidload在等待gcdtest3执行完,而 NSLog(@"奔溃了");则在等待vivididload执行完.所以直接就卡死了.两个相互等待.直接奔溃了.
那要怎么解决呢,把同步变为异步,就可以.

看了上面的各个组合的实现,其实我们把队列和任务来个比喻,方便更好的理解.
假设有一个患者打针看病的场景,
如果是串行同步的,那就是有一个医生,然后各个患者按照顺讯排队一个一个来.
异步串行又是什么呢?它就好比是:患者都排成了一排,但是这个时候医生就只有一个,所以看病的顺序还是要按先来后到的一个一个的来
同步并发呢?这个恰恰相反了,现在医生有好多个了,但是患者是一个一个的排成一列了.只能一个看完另外一个才能接着看病.
异步并发: 患者排成一排,然后医生也有多个,一个对应一个患者,同时开始看病

4 GCD线程间的通信

在子线程执行耗时操作,执行完成之后回到主线程去刷新UI

-(void)GCDrefreshUI{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"执行耗时操作");
        sleep(4);
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"查看当前的线程:--%@",[NSThread currentThread]);
        });
        
    });
}
5 GCD其他常见的方法

1 栅栏方法(dispatch_barrier_async)
我们有时需要异步执行两组操作,第一组操作执行完之后,采取执行第二组操作.这个时候我们就需要用到栅栏方法(dispatch_barrier_async)

-(void)barray_gcdtest{
    
    //无法拦截全局队列中,只能是自定义的
    dispatch_queue_t serqueue = dispatch_queue_create("serqueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(serqueue, ^{
        NSLog(@" 1-线程:--%@",[NSThread currentThread]);
    });
    dispatch_async(serqueue, ^{
        NSLog(@"2-线程:--%@",[NSThread currentThread]);
    });
    
    //添加栅栏
    dispatch_barrier_async(serqueue, ^{
        sleep(1);
        NSLog(@"当前线程:--%@",[NSThread currentThread]);
    });
    
    dispatch_async(serqueue, ^{
        NSLog(@"3-线程:--%@",[NSThread currentThread]);
    });
    dispatch_async(serqueue, ^{
        NSLog(@"4-线程:--%@",[NSThread currentThread]);
    });
    dispatch_async(serqueue, ^{
        NSLog(@"5-线程:--%@",[NSThread currentThread]);
    });
}
打印结果:2022-05-10 10:56:46.368343+0800 多线程demo[10542:472778] 2-线程:--<NSThread: 0x6000039f8080>{number = 7, name = (null)}
2022-05-10 10:56:46.368346+0800 多线程demo[10542:472780]  1-线程:--<NSThread: 0x6000039b9380>{number = 6, name = (null)}
2022-05-10 10:56:47.371021+0800 多线程demo[10542:472780] 当前线程:--<NSThread: 0x6000039b9380>{number = 6, name = (null)}
2022-05-10 10:56:47.371462+0800 多线程demo[10542:472780] 3-线程:--<NSThread: 0x6000039b9380>{number = 6, name = (null)}
2022-05-10 10:56:47.371487+0800 多线程demo[10542:472778] 4-线程:--<NSThread: 0x6000039f8080>{number = 7, name = (null)}
2022-05-10 10:56:47.371547+0800 多线程demo[10542:472782] 5-线程:--<NSThread: 0x6000039a4080>{number = 4, name = (null)}

如果没有dispatch_barrier_async,执行结果应该是无序的.但加入dispatch_barrier_async以后,就相当于把1 2扒拉到一边了,剩下的是另外一个,先执行1 2这组,然后其余的这组.同时还有一个注意的点,只有在自定义的队列中才能 栅栏才是有效果的.如果是一个全局队列是无法拦截的.
2 group组
当我们想知道异步+并发,什么时候执行完的时候,就需要用到dispatch_group_t.把要监测的线程放到group中,然后有一个dispatch_group_notify方法,通知执行完成.

-(void)group_gcdtest{
    
    dispatch_group_t testGroup = dispatch_group_create();
    dispatch_queue_t glopeQueue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_async(testGroup, glopeQueue, ^{
        sleep(1);
        NSLog(@"线程1 -%@",[NSThread currentThread]);
    });
    dispatch_group_async(testGroup, glopeQueue, ^{
        sleep(4);
        NSLog(@"线程2 -%@",[NSThread currentThread]);
    });
    dispatch_group_async(testGroup, glopeQueue, ^{
        sleep(3);
        NSLog(@"线程3 -%@",[NSThread currentThread]);
    });
    
    dispatch_group_notify(testGroup, glopeQueue, ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"主线程 -%@",[NSThread currentThread]);
        });
    });    
}
打印结果:2022-05-10 11:21:44.719382+0800 多线程demo[10756:484771] 线程1 -<NSThread: 0x6000029ec3c0>{number = 7, name = (null)}
2022-05-10 11:21:46.716449+0800 多线程demo[10756:484772] 线程3 -<NSThread: 0x6000029e5180>{number = 5, name = (null)}
2022-05-10 11:21:47.716715+0800 多线程demo[10756:484776] 线程2 -<NSThread: 0x60000298c240>{number = 6, name = (null)}
2022-05-10 11:21:47.717220+0800 多线程demo[10756:484556] 主线程 -<_NSMainThread: 0x6000029e8a80>{number = 1, name = main}

从打印的结果就可以发现,线程都执行完以后,会调用notyf的方法.
3 快速迭代(dispatch_apply)
通常我们会使用for循环遍历,GCD提供了一个dispatch_apply.它可以按照指定的次数将任务追加到指定的队列中,并等贷全部任务执行结束. 如果实在串行队列中使用,就和for循环一样,是按循序执行的.所以实际使用的时候一般是并发队列

//快速迭代 遍历
-(void)apply_gcdtest{
    
    dispatch_queue_t globequeue = dispatch_get_global_queue(0, 0);
    NSLog(@"开始迭代");
    dispatch_apply(6, globequeue, ^(size_t iteration) {
        sleep(1);
        NSLog(@"线程- %ld = %@",iteration,[NSThread currentThread]);
    });
    NSLog(@"迭代完成");
}
结果:2022-05-10 11:32:44.019881+0800 多线程demo[10852:490803] 开始迭代
2022-05-10 11:32:45.020560+0800 多线程demo[10852:490803] 线程- 0 = <_NSMainThread: 0x6000013a08c0>{number = 1, name = main}
2022-05-10 11:32:45.020557+0800 多线程demo[10852:490870] 线程- 1 = <NSThread: 0x6000013ae700>{number = 7, name = (null)}
2022-05-10 11:32:45.020556+0800 多线程demo[10852:490869] 线程- 3 = <NSThread: 0x6000013f8300>{number = 5, name = (null)}
2022-05-10 11:32:45.020562+0800 多线程demo[10852:490866] 线程- 4 = <NSThread: 0x6000013a0580>{number = 8, name = (null)}
2022-05-10 11:32:45.020562+0800 多线程demo[10852:490864] 线程- 2 = <NSThread: 0x6000013e1d80>{number = 6, name = (null)}
2022-05-10 11:32:45.020573+0800 多线程demo[10852:490868] 线程- 5 = <NSThread: 0x6000013f9d40>{number = 3, name = (null)}
2022-05-10 11:32:45.020857+0800 多线程demo[10852:490803] 迭代完成

要注意的是,和异步+并发不同,快速迭代需要执行完才能继续执行下一个.这也就是为什么"迭代完成"最后打印出来.
4 信号量
GCD 中的信号量是指Dispatch Semaphore,使用计数来完成这个功能.计数小于0时等待,不可通过.计数为0或者大于0是,不等待,可通过. 信号量的主要作用是:
-1 保持线程同步,将异步执行任务转化为同步执行任务
-2 保证线程安全,为线程加锁.

-(void)semaphore_gcdtest{
    
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    //创建信号量 大于0 不会同步执行  =0 正常使用 会同步执行
    dispatch_semaphore_t semapphore=dispatch_semaphore_create(0);
    
    dispatch_async(globalQueue, ^{
       
        sleep(2);
        NSLog(@"当前的线程是:%@",[NSThread currentThread]);
        //解锁 让信号量+1
        dispatch_semaphore_signal(semapphore);
    });
    
    NSLog(@"开始枷锁");
    //让信号量-1,当信号量<0时候, 会阻塞所在的线程.
    dispatch_semaphore_wait(semapphore, DISPATCH_TIME_FOREVER);
    NSLog(@"解锁执行完成");
}
结果为:2022-05-10 11:59:28.150768+0800 多线程demo[11166:506297] 开始枷锁
2022-05-10 11:59:30.152631+0800 多线程demo[11166:506502] 当前的线程是:<NSThread: 0x60000067c380>{number = 6, name = (null)}
2022-05-10 11:59:30.153033+0800 多线程demo[11166:506297] 解锁执行完成

总结:创建的是同步信号,信号量为0,创建了一个全局并发队列 异步执行,创建线程异步执行thread1,主线程打印 "开始枷锁",这个时候给线程枷锁,信号总量<0,阻塞了主线程.子线程还继续执行.
子线程然后解锁,信号量变为0,主线程继续执行.所以才打印出上述的结果.
下面再来一个例子,模拟抢票,来加深理解

-(void)ticket_gcdtest{
    
    dispatch_queue_t golbaequeue=dispatch_get_global_queue(0, 0);
    self.semaphore = dispatch_semaphore_create(1);
    
    dispatch_async(golbaequeue, ^{
        [self ticket_num];
    });
    dispatch_async(golbaequeue, ^{
        [self ticket_num];
    });
    dispatch_async(golbaequeue, ^{
        [self ticket_num];
    });
    dispatch_async(golbaequeue, ^{
        [self ticket_num];
    });
}

-(void)ticket_num{
    
    while (1) {
        dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
        if (self.ticketnum>0) {
            self.ticketnum--;
            NSLog(@"剩余票数 = %ld  线程:%@",self.ticketnum,[NSThread currentThread]);
            sleep(1);
            
        }else{
            NSLog(@"售完");
            dispatch_semaphore_signal(self.semaphore);
            break;
        }
        dispatch_semaphore_signal(self.semaphore);   
    }
}
结果为:2022-05-10 14:47:36.466909+0800 多线程demo[12725:587462] 剩余票数 = 9  线程:<NSThread: 0x600002f3e080>{number = 6, name = (null)}
2022-05-10 14:47:37.469077+0800 多线程demo[12725:587463] 剩余票数 = 8  线程:<NSThread: 0x600002f6ca80>{number = 5, name = (null)}
2022-05-10 14:47:38.474606+0800 多线程demo[12725:587469] 剩余票数 = 7  线程:<NSThread: 0x600002f4e440>{number = 8, name = (null)}
2022-05-10 14:47:39.478234+0800 多线程demo[12725:587464] 剩余票数 = 6  线程:<NSThread: 0x600002f3a780>{number = 9, name = (null)}
2022-05-10 14:47:40.483744+0800 多线程demo[12725:587462] 剩余票数 = 5  线程:<NSThread: 0x600002f3e080>{number = 6, name = (null)}
2022-05-10 14:47:41.489249+0800 多线程demo[12725:587463] 剩余票数 = 4  线程:<NSThread: 0x600002f6ca80>{number = 5, name = (null)}
2022-05-10 14:47:42.494754+0800 多线程demo[12725:587469] 剩余票数 = 3  线程:<NSThread: 0x600002f4e440>{number = 8, name = (null)}
2022-05-10 14:47:43.500287+0800 多线程demo[12725:587464] 剩余票数 = 2  线程:<NSThread: 0x600002f3a780>{number = 9, name = (null)}
2022-05-10 14:47:44.505797+0800 多线程demo[12725:587462] 剩余票数 = 1  线程:<NSThread: 0x600002f3e080>{number = 6, name = (null)}
2022-05-10 14:47:45.511286+0800 多线程demo[12725:587463] 剩余票数 = 0  线程:<NSThread: 0x600002f6ca80>{number = 5, name = (null)}
2022-05-10 14:47:46.515650+0800 多线程demo[12725:587469] 售完
2022-05-10 14:47:46.516032+0800 多线程demo[12725:587464] 售完
2022-05-10 14:47:46.516473+0800 多线程demo[12725:587462] 售完
2022-05-10 14:47:46.516859+0800 多线程demo[12725:587463] 售完

为什么会出现这种结果呢?首先,这是并发+异步的组合,所以创建了新的子线程.再看ticket_num方法中的执行,是一个死循环,进入循环的时候先加锁,信号量-1,然后判断票数,当售完的时候 break返回.
我们接着看子线程,第一个进入的子线程实现ticket_num方法,这个时候经过加锁,信号量为0,变成了同步.其他子线程进入了方法,这个时候信号量为-1 -2 -3,阻塞了线程,等待.当出票完成后,解锁,信号量+1.这个时候执行之前信号量为-1的线程.如此循环执行.直到售完.
5 dispatch_once_t(单例)
创建单例

+ (instantClass *)sharedClient {
static instantClass *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[instantClass alloc] init];
});
return _sharedClient;
}
NSOperation

NSOperation和NSOperationQueue是对GCD的一层封装,NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类NSInvocationOperation和NSBlockOperation。

-(void)opertion_init{
    
    NSInvocationOperation *opertion=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run) object:nil];
    [opertion start];
    
    NSBlockOperation *blockopertion = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"block A");
    }];
    
    [blockopertion addExecutionBlock:^{
        NSLog(@"block B");
    }];
    [blockopertion addExecutionBlock:^{
        NSLog(@"block C");
    }];
    [blockopertion start];
    
}

-(void)run{
    
    NSLog(@"nsinvocation");
}

2、NSOperation之间可以设置依赖来保证执行顺序
一定要让操作A执行完后,才能执行操作B,

-(void)testdepence{
    
    NSOperationQueue *queue=[[NSOperationQueue alloc]init];
    
    NSBlockOperation *block1=[NSBlockOperation blockOperationWithBlock:^{    
        NSLog(@"A");
    }]; 
    NSBlockOperation *block2=[NSBlockOperation blockOperationWithBlock:^{    
        NSLog(@"B");
    }];
    NSBlockOperation *block3=[NSBlockOperation blockOperationWithBlock:^{    
        NSLog(@"c");
    }]; 
    [block3 addDependency:block1];
    [block1 addDependency:block2]; 
}

多线程的一些基础知识基本就这些了,后续会不断的完善,添加关于死锁 自旋锁 互斥锁的一些相关知识.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容

  • 前言:本文章摘自作者devsongxx,链接:https://www.jianshu.com/p/8ff1eaeb...
    贾小敏1234阅读 488评论 0 0
  • 了解多线程,首先我们需要了解以下知识 进程 ●进程是指在系统中正在运行的一个应用程序,就是一段程序的执行过程,我们...
    赵哥窟阅读 96评论 0 0
  • GCD GCD是纯C语言的。 串行队列(DISPATCH_QUEUE_SERIAL):一次只能执行一个任务,队列中...
    jumpingfrog0阅读 343评论 0 1
  • 多线程 优缺点,实际应用多线程比较 死锁:向同一个/当前的串行队列添加同步sync操作任务,会产生死锁,新等旧,旧...
    愤斗的小蚂蚁阅读 1,013评论 0 4
  • iOS中的常见多线程方案 GCD的常用函数 GCD中有2个用来执行任务的函数 用同步的方式执行任务dispatch...
    斑驳的流年无法释怀阅读 299评论 0 2