iOS底层原理 - 八大锁分析

回顾之前

前文讲到多线程原理,线程安全、线程阻塞、线程使用等;这节我们来分析一下有关线程安全的一部分:锁,线程锁。

锁初识

我们所用到的锁,是为了解决线程安全问题;一段代码段在同一个时间只能允许被有限个线程访问,解决资源竞争导致的数据紊乱;八大线程锁即:1.NSClock,2.NSConditionLock,3.NSCondition,4.NSRecursiveLock,5.@synchronized,6.dispatch_semaphore,7.OSSpinLock,8.pthread_mutex

线程资源竞争问题举例:

@property (nonatomic, assign) NSInteger totalNum;

-(void)viewDidLoad {

    _totalNum = 10;

    [self threadSecuretTest];

}

   

- (void)runMoreTickets{

    if (_totalNum == 0) {

        return;

    }

    sleep(0.2);

    NSLog(@"%ld (currentThreadName:%@, currentThreadCount:%ld)",_totalNum --, [NSThread currentThread].name,[NSThread currentThread].stackSize);

}

- (void)threadSecuretTest {

    dispatch_queue_t queuet = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_async(queuet, ^{

        for (int i = 0; i < 5; i++) {

            [self runMoreTickets];

        }

    });

    

    dispatch_async(queuet, ^{

        for (int i = 0; i < 5; i++) {

            [self runMoreTickets];

        }

    });

    dispatch_async(queuet, ^{

        for (int i = 0; i < 5; i++) {

            [self runMoreTickets];

        }

    });

}

结果:** 2020-07-20 09:47:15.702720+0800 Test-OC[86022:6943874] 8 (currentThreadName:, currentThreadCount:524288)**

2020-07-20 09:47:15.702720+0800 Test-OC[86022:6943879] 10 (currentThreadName:, currentThreadCount:524288)

2020-07-20 09:47:15.702720+0800 Test-OC[86022:6943873] 9 (currentThreadName:, currentThreadCount:524288)

2020-07-20 09:47:15.702897+0800 Test-OC[86022:6943874] 6 (currentThreadName:, currentThreadCount:524288)

2020-07-20 09:47:15.702898+0800 Test-OC[86022:6943879] 7 (currentThreadName:, currentThreadCount:524288)

2020-07-20 09:47:15.702897+0800 Test-OC[86022:6943873] 6 (currentThreadName:, currentThreadCount:524288)

2020-07-20 09:47:15.703034+0800 Test-OC[86022:6943879] 4 (currentThreadName:, currentThreadCount:524288)

2020-07-20 09:47:15.703028+0800 Test-OC[86022:6943874] 5 (currentThreadName:, currentThreadCount:524288)

2020-07-20 09:47:15.703057+0800 Test-OC[86022:6943873] 3 (currentThreadName:, currentThreadCount:524288)

2020-07-20 09:47:15.703122+0800 Test-OC[86022:6943879] 2 (currentThreadName:, currentThreadCount:524288)

2020-07-20 09:47:15.703385+0800 Test-OC[86022:6943874] 1 (currentThreadName:, currentThreadCount:524288)

锁详解

1.NSClock

  互斥锁,加锁过程是按照队列的形式(FIFO),先进先出的原则。
@protocol NSLocking

- (void)lock;
- (void)unlock;

@end

@interface NSLock : NSObject <NSLocking> {
@private
    void *_priv;
}

- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

NSLock遵循NSLocking协议。lock是加锁,unLock是解锁,tryLock是尝试加锁,失败的话会返回NO;lockBeforeDate:是在指定时间前尝试加锁,要是在指定时间前加锁失败则返回NO。

看个例子
// 主线程

    NSLock * lock = [[NSLock alloc] init];

    dispatch_queue_t group = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 1

    dispatch_async(group, ^{

        [lock lock];

        sleep(3);

        NSLog(@"线程1");

        [lock unlock];

        NSLog(@"线程1解锁成功");

    });

    // 2

    dispatch_async(group, ^{

        sleep(2);

        [lock lock];

        NSLog(@"线程2");

        [lock unlock];

    });

2020-07-20 10:23:55.627833+0800 Test-OC[86238:6963970] 线程1

2020-07-20 10:23:55.628053+0800 Test-OC[86238:6963970] 线程1解锁成功

2020-07-20 10:23:55.628061+0800 Test-OC[86238:6963971] 线程2 

由上面打印结果可以看出,线程1加锁时阻塞了线程2,线程二加锁失败。3s后线程1解锁了,线程二才加锁成功;

  建议使用tryLock ,尝试加锁,成功返回YES,再使用解锁,这样不会阻塞线程。

  如果是三个线程,那么一个线程在加锁的时候,其余请求锁的线程将形成一个等待队列,按先进先出原则,这个结果可以通过修改线程优先级进行测试得出。

2.NSConditionLock

和NSLock类似,都遵循NSLocking协议,不过多了condition属性。

@interface NSConditionLock : NSObject <NSLocking> {

@private

    void *_priv;

}

- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;

@property (readonly) NSInteger condition;

- (void)lockWhenCondition:(NSInteger)condition;

- (BOOL)tryLock;

- (BOOL)tryLockWhenCondition:(NSInteger)condition;

- (void)unlockWithCondition:(NSInteger)condition;

- (BOOL)lockBeforeDate:(NSDate *)limit;

- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

举个栗子:

NSConditionLock * conditionLock = [[NSConditionLock alloc] initWithCondition:0];

    dispatch_queue_t group = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 1

    dispatch_async(group, ^{

        [conditionLock lockWhenCondition:1];

        NSLog(@"线程1");

        sleep(2);

        [conditionLock unlock];

    });

    

    // 2

    dispatch_async(group, ^{

        sleep(1);

        if ([conditionLock tryLockWhenCondition:0]) {

            NSLog(@"线程2");

            [conditionLock unlockWithCondition:2];

            NSLog(@"线程2解锁成功");

        }else{

            NSLog(@"线程2尝试加锁失败");

        }

    });

    // 3

    dispatch_async(group, ^{

        sleep(2);

        if ([conditionLock tryLockWhenCondition:2]) {

            NSLog(@"线程3");

            [conditionLock unlock];

            NSLog(@"线程3解锁成功");

        }else{

            NSLog(@"线程3尝试加锁失败");

        }

    });

    // 4

    dispatch_async(group, ^{

        sleep(3);

        if ([conditionLock tryLockWhenCondition:2]) {

            NSLog(@"线程4");

            [conditionLock unlockWithCondition:1];

            NSLog(@"线程4解锁成功");

        }else{

            NSLog(@"线程4尝试加锁失败");

        }

    });

2020-07-20 13:53:10.877963+0800 Test-OC[88096:7132297] 线程2

2020-07-20 13:53:10.878170+0800 Test-OC[88096:7132297] 线程2解锁成功

2020-07-20 13:53:11.876597+0800 Test-OC[88096:7132296] 线程3

2020-07-20 13:53:11.876787+0800 Test-OC[88096:7132296] 线程3解锁成功

2020-07-20 13:53:12.878735+0800 Test-OC[88096:7132295] 线程4

2020-07-20 13:53:12.878914+0800 Test-OC[88096:7132295] 线程4解锁成功

2020-07-20 13:53:12.878923+0800 Test-OC[88096:7132298] 线程1

上述代码先输出线程2而没先输出线程1,由于condition1开始处于未满足处于解锁状态,会使线程1处于waiting状态,到线程4解锁出1时才满足条件;tryLockWhenCondition 即使未满足条件也不会返回NO,不会阻塞当前线程。

可以看出,NSConditionLock还可实现任务依赖关系

3.NSCondition

NScondition的实例化对象作为锁和锁的检查器使用;不像其他锁一样,先轮询,而是直接进入waiting状态当有其他 线程执行signal或者broadcast方法时,线程被唤醒,继续之后的方法;锁上之后其他线程仍然可以上锁,之后可以根据条件判断是否继续运行线程,即线程是否进入waiting状态。

@interface NSCondition : NSObject <NSLocking> {

@private

    void *_priv;

}

- (void)wait;

- (BOOL)waitUntilDate:(NSDate *)limit;

- (void)signal;

- (void)broadcast;

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

举例用法:

NSCondition * condition = [[NSCondition alloc] init];

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    NSMutableArray *testArr = [NSMutableArray array];

    dispatch_async(queue, ^{

        [condition lock];

        while (!testArr.count) {

            [condition wait];

        }

        NSLog(@"线程1");

        [testArr removeAllObjects];

        NSLog(@"testArr removeAllObject");

        [condition unlock];

    });

    

    dispatch_async(queue, ^{

        [condition lock];

        [testArr addObject:@1];

        NSLog(@"线程2, textArrAddObject");

        [condition signal];

        [condition unlock];

    });

2020-07-20 14:39:39.001432+0800 Test-OC[88547:7161017] 线程2, textArrAddObject

2020-07-20 14:39:39.001750+0800 Test-OC[88547:7161016] 线程1

2020-07-20 14:39:39.001881+0800 Test-OC[88547:7161016] testArr removeAllObject

上面结果可以看出,condition上锁后还是可以继续上锁,并不会阻塞线程;使用场景更多在锁定条件对象,测试是否安全的执行以下任务。

其中signal 和broadcast 方法区别在于,signal是信号量控制,调用一次只能唤起一次线程等待;想要唤醒多次需要多次调用;broadcast则是可以唤醒所有线程等待。若无线程等待,调用两方法都无作用。

4.NSRecursiveLock

递归锁,他与NSLock区别在于,NSRecursiveLock可以在一个线程里重复加锁(由于单线程是按顺序执行,不会出现资源竞争的情况);NSRecursiveLock会记住上锁和解锁的次数,只有平衡了才会释放锁。其他线程才能上锁成功

**@interface** NSRecursiveLock : NSObject <NSLocking> {

**@private**

​ **void** *_priv;

}

- (**BOOL**)tryLock;

- (**BOOL**)lockBeforeDate:(NSDate *)limit;

**@property** (**nullable**, **copy**) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

**@end**

使用举例:


NSRecursiveLock * lock = [[NSRecursiveLock alloc] init];

​ dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

​

​ dispatch_async(queue, ^{

​ **static**  **void** (^RecursiveLockBlock)(**int**);

​ RecursiveLockBlock = ^(**int** value){

​ [lock lock];

​ **if** (value > 0) {

​ NSLog(@"Value: %d",value);

​ RecursiveLockBlock(value - 1);

​ }

​ };

​ RecursiveLockBlock(3);

​ });

**2020-07-20 15:58:38.965564+0800 Test-OC[89057:7199031] Value: 3**

**2020-07-20 15:58:38.965825+0800 Test-OC[89057:7199031] Value: 2**

**2020-07-20 15:58:38.965924+0800 Test-OC[89057:7199031] Value: 1**

从上可知,使用NSRecursiveLock 在加锁未解锁情况下重复加锁而不会阻塞线程,要是替换成NSLock加锁未解锁继续加锁就会阻塞线程,下面代码就不会执行了;递归锁就是为了解决这种问题。

5.@sychronized

​ 对象级别锁,互斥锁。@sychronized(object) ,只有object相同情况下才满足互斥条件。

​ 举个🌰


dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

​ dispatch_async(queue, ^{

​ **@synchronized** (**self**) {

​ NSLog(@"线程1");

​ }

​ });

​ dispatch_async(queue, ^{

​ sleep(2);

​ **@synchronized** (**self**) {

​ NSLog(@"线程2");

​ }

​ });

@synchronized (object) 用法比较简单,在方法内已经处理好加解锁;不过性能相对是较差的那种;

6.dispatch_semaphore

​ 信号量,控制线程最大并发数,也是锁的一种使用

dispatch_semaphore_t

// 传入值必须 >=0, 若传入为 0 则阻塞线程并等待timeout,时间到后会执行其后的语句

dispatch_semaphore_create(long value);

// 可以理解为 lock,会使得 signal-1

dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

可以理解为 unlock,会使得 signal+1

dispatch_semaphore_signal(dispatch_semaphore_t dsema);

使用举例:


dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 2);

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{

NSLog(@"线程1 线程等待");

dispatch_semaphore_wait(semaphore, overTime);

NSLog(@"线程1");

dispatch_semaphore_signal(semaphore);

NSLog(@"线程1 signal发信号");

});

dispatch_async(queue, ^{

NSLog(@"线程2 线程等待");

dispatch_semaphore_wait(semaphore, overTime);

NSLog(@"线程2");

dispatch_semaphore_signal(semaphore);

NSLog(@"线程2 signal发信号");

});

**2020-07-23 16:39:02.668485+0800 Test-OC[2783:214582] 线程2 线程等待**

**2020-07-23 16:39:02.668485+0800 Test-OC[2783:214584] 线程1 线程等待**

**2020-07-23 16:39:02.668794+0800 Test-OC[2783:214584] 线程1**

**2020-07-23 16:39:02.668812+0800 Test-OC[2783:214582] 线程2**

**2020-07-23 16:39:02.668920+0800 Test-OC[2783:214584] 线程1 signal发信号**

**2020-07-23 16:39:02.668911+0800 Test-OC[2783:214582] 线程2 signal发信号**

从上述结果可以看出,在线程等待后才开始后续的方法执行;类似于教室座位计算,人满了外面会处于等待状态,有座位才安排;设置dispatch_semaphore_create(0) 为0 时overTime生效,等待满足实际才开始执行下面的任务;

7.OSSPinkLock

自旋锁,效率为最高(iOS10之后被官方认定为不安全的锁,不建议使用)自旋锁不会让等待的进入睡眠状态

需导入头文件

// #import <libkern/OSAtomic.h>

使用举例:


**__block** OSSpinLock osLock = OS_SPINLOCK_INIT;

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{

NSLog(@"线程1 准备上锁");

OSSpinLockLock(&osLock);

NSLog(@"线程1");

OSSpinLockUnlock(&osLock);

NSLog(@"线程1 解锁");

});

dispatch_async(queue, ^{

NSLog(@"线程2 准备上锁");

OSSpinLockLock(&osLock);

NSLog(@"线程2");

OSSpinLockUnlock(&osLock);

NSLog(@"线程2 解锁");

});

**2020-07-26 23:48:49.448361+0800 Test-OC[7829:456571] 线程1 准备上锁**

**2020-07-26 23:48:49.448361+0800 Test-OC[7829:456570] 线程2 准备上锁**

**2020-07-26 23:48:49.448656+0800 Test-OC[7829:456570] 线程2**

**2020-07-26 23:48:49.448770+0800 Test-OC[7829:456570] 线程2 解锁**

**2020-07-26 23:48:49.448951+0800 Test-OC[7829:456571] 线程1**

**2020-07-26 23:48:49.449080+0800 Test-OC[7829:456571] 线程1 解锁**

从运行结果可以看出我们锁住线程1和线程二时,线程二会一直处于线程等待状态,直到线程1解锁完成,线程二会立即执行;

如果我们改变一些解锁状态看一下运行是否受影响


/// 将线程一解锁关闭

dispatch_async(queue, ^{

NSLog(@"线程1 准备上锁");

OSSpinLockLock(&osLock);

NSLog(@"线程1");

// OSSpinLockUnlock(&osLock);

NSLog(@"线程1 解锁");

}); ....

**2020-07-27 00:03:05.802912+0800 Test-OC[8003:465228] 线程1 准备上锁**

**2020-07-27 00:03:05.802912+0800 Test-OC[8003:465230] 线程2 准备上锁**

**2020-07-27 00:03:05.803140+0800 Test-OC[8003:465228] 线程1**

**2020-07-27 00:03:05.803245+0800 Test-OC[8003:465228] 线程1 解锁**

可以看出线程1未解锁线程2也不会执行,所以oslocklockunlock需要成对存在

OS_SPINLOCK_INIT: 默认值为 0,在 locked 状态时就会大于 0unlocked状态下为 0

OSSpinLockLock(&oslock):上锁,参数为 OSSpinLock 地址

OSSpinLockUnlock(&oslock):解锁,参数为 OSSpinLock 地址

OSSpinLockTry(&oslock):尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO

8.pthread_mutex

递归锁, ibireme在《不再安全的 OSSpinLock》文章中提到,性能最好的OSSPinkLock不再安全已将osspinklock替换为pthread_mutex

需提前导入头文件:

// #import "pthread.h"

使用举例:

static pthread_mutex_t p_lock;

- (void)pthread_mutex_t_Text{

    pthread_mutex_init(&p_lock, NULL);

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_async(queue, ^{

        NSLog(@"线程1 准备加锁");

        pthread_mutex_lock(&p_lock);

        sleep(2);

        NSLog(@"线程1");

        pthread_mutex_unlock(&p_lock);

        NSLog(@"线程1 解锁成功");

    });

    dispatch_async(queue, ^{

        NSLog(@"线程2 准备加锁");

        pthread_mutex_lock(&p_lock);

        sleep(2);

        NSLog(@"线程2");

        pthread_mutex_unlock(&p_lock);

        NSLog(@"线程2 解锁成功");

    });

}

2020-07-27 10:08:05.713354+0800 Test-OC[8616:492609] 线程2 准备加锁

2020-07-27 10:08:05.713354+0800 Test-OC[8616:492610] 线程1 准备加锁

2020-07-27 10:08:07.717438+0800 Test-OC[8616:492610] 线程1

2020-07-27 10:08:07.717653+0800 Test-OC[8616:492610] 线程1 解锁成功

2020-07-27 10:08:09.720069+0800 Test-OC[8616:492609] 线程2

2020-07-27 10:08:09.720386+0800 Test-OC[8616:492609] 线程2 解锁成功

从运行结果可以看出,原理和osspinkLock类似,不过ossPinkLocktry(&lock)pthread_mutex_trylock区别在于后者返回的是0,否则就是一个错误提示码,前者则返回YES/NO

YYCache中pthread_mutex_t用法:

YYCache Lock.png
pthread_mutex_t(recursive)
 pthread_mutex_t pLock;
 pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr); //初始化attr并且给它赋予默认
 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //设置锁类型,这边是设置为递归锁
 pthread_mutex_init(&pLock, &attr);
pthread_mutexattr_destroy(&attr); //销毁一个属性对象,在重新进行初始化之前该结构不能重新使用</pre>

 pthread_mutex还有一种递归锁的用法,从上面用法可知,一般需要加锁之后只能一个线程对象访问,不解锁下个线程是不可访问的。递归锁,可以在一个线程内重复加锁而不释放

使用举例:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_async(queue, ^{

        static void (^recursiveBlock)(int);

        recursiveBlock = ^(int value){

            pthread_mutex_lock(&p_lock);

            if (value > 0) {

                NSLog(@"value: %d",value);

                recursiveBlock(value-1);

            }

            pthread_mutex_unlock(&p_lock);

        };

        recursiveBlock(7);

    });

2020-07-27 10:58:07.534516+0800 Test-OC[9103:517900] value: 7

2020-07-27 10:58:07.534677+0800 Test-OC[9103:517900] value: 6

2020-07-27 10:58:07.534803+0800 Test-OC[9103:517900] value: 5

2020-07-27 10:58:07.534899+0800 Test-OC[9103:517900] value: 4

2020-07-27 10:58:07.534992+0800 Test-OC[9103:517900] value: 3

2020-07-27 10:58:07.535128+0800 Test-OC[9103:517900] value: 2

2020-07-27 10:58:07.535833+0800 Test-OC[9103:517900] value: 1

总结

图引自ibeme,性能对比.png

参考:https://www.jianshu.com/p/b3ab3d390903

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