NSThead的進階使用和簡單探討

image

概述

NSThread類是一個繼承于NSObjct類的輕量級類顽腾。一個NSThread對象就代表一個線程近零。它需要管理線程的生命周期、同步抄肖、加鎖等問題,因此會產(chǎn)生一定的性能開銷窖杀。
使用NSThread類可以在特定的線程中被調(diào)用某個OC方法漓摩。當需要執(zhí)行一個冗長的任務(wù),并且不想讓這個任務(wù)阻塞應(yīng)用中的其他部分入客,尤其為了避免阻塞app的主線程(因為主線程用于處理用戶界面展示交互和事件相關(guān)的操作),這個時候非常適合使用多線程管毙。線程也可以將一個龐大的任務(wù)分為幾個較小的任務(wù),從而提高多核計算機的性能桌硫。

NSThread類在運行期監(jiān)聽一個線程的語義和NSOperation類是相似的夭咬。比如取消一個線程或者決定一個任務(wù)執(zhí)行完后這個線程是否存在。

本文將會從這幾個方面開始探討NSThread

image

方法屬性的介紹


初始化(創(chuàng)建)一個NSThread對象

// 返回一個初始化的NSThread對象
- (instancetype)init
// 返回一個帶有多個參數(shù)的初始化的NSThread對象
// selector :線程執(zhí)行的方法,最多只能接收一個參數(shù)
// target :selector消息發(fā)送的對象
// argument : 傳給selector的唯一參數(shù)铆隘,也可以是nil
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument );
// iOS 10
- (instancetype)initWithBlock:(void (^)(void))block;

啟動一個線程卓舵。

// 開辟一個新的線程,并且使用特殊的選擇器Selector作為線程入口,調(diào)用完畢后膀钠,會馬上創(chuàng)建并開啟新線程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
 // iOS 10
+ (void)detachNewThreadWithBlock:(void (^)(void))block;
// 啟動接受者
- (void)start;
// 線程體方法掏湾,線程主要入口,start 后執(zhí)行
// 該方法默認實現(xiàn)了目標(target)和選擇器(selector)肿嘲,用于初始化接受者和調(diào)用指定目標(target)的方法融击。如果子類化NSThread,需要重寫這個方法并且用它來實現(xiàn)這個線程主體雳窟。在這種情況下尊浪,是不需要調(diào)用super方法的。
// 不應(yīng)該直接調(diào)用這個方法封救。你應(yīng)該通過調(diào)用啟動方法開啟一個線程拇涤。
- (void)main;

使用initWithTarget:selector:initWithBlock:兴泥、detachNewThreadSelector:工育,detachNewThreadWithBlock:創(chuàng)建線程都是異步線程。

停止一個線程

// 阻塞當前線程搓彻,直到特定的時間如绸。
+ (void)sleepUntilDate:(NSDate *)date;
// 讓線程處于休眠狀態(tài)嘱朽,直到經(jīng)過給定的時間間隔
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 終止當前線程
+ (void)exit;
// 改變接收者的取消狀態(tài),來表示它應(yīng)該終止
- (void)cancel;

決定線程狀態(tài)

// 接收者是否存在
@property (readonly, getter=isExecuting) BOOL executing;
// 接收者是否結(jié)束執(zhí)行
@property (readonly, getter=isFinished) BOOL finished;
// 接收者是否取消
@property (readonly, getter=isCancelled) BOOL cancelled;

主線程相關(guān)

// 當前線程是否是主線程
@property (class, readonly) BOOL isMainThread;
// 接受者是否是主線程
@property (readonly) BOOL isMainThread;
// 獲取主線程的對象
@property (class, readonly, strong) NSThread *mainThread;

執(zhí)行環(huán)境

// 這個app是否是多線程
+ (BOOL)isMultiThreaded;
// 返回當前執(zhí)行線程的線程對象怔接。
@property (class, readonly, strong) NSThread *currentThread;
// 返回一個數(shù)組搪泳,包括回調(diào)堆棧返回的地址
@property (class, readonly, copy) NSArray<NSNumber *> *callStackReturnAddresses ;
// 返回一個數(shù)組,包括回調(diào)堆棧信號
@property (class, readonly, copy) NSArray<NSString *> *callStackSymbols;

線程屬性相關(guān)

// 線程對象的字典
@property (readonly, retain) NSMutableDictionary *threadDictionary;

NSAssertionHandlerKey
// 接收者的名字
@property (nullable, copy) NSString *name;
// 接收者的對象大小扼脐,以byte為單位
@property NSUInteger stackSize;

線程優(yōu)先級

// 線程開啟后是個只讀屬性
@property NSQualityOfService qualityOfService;
// 返回當前線程的優(yōu)先級
+ (double)threadPriority;
// 接受者的優(yōu)先級岸军,已經(jīng)廢棄,使用qualityOfService代替
@property double threadPriority;
// 設(shè)置當前線程的優(yōu)先級瓦侮。設(shè)置線程的優(yōu)先級(0.0 - 1.0艰赞,1.0最高級)
+ (BOOL)setThreadPriority:(double)p;

通知

// 未被實現(xiàn),沒有實際意義肚吏,保留項
NSDidBecomeSingleThreadedNotification
// 在線程退出前方妖,一個NSThread對象收到到退出消息時會發(fā)送這個通知。
NSThreadWillExitNotification
// 當?shù)谝粋€線程啟動時會發(fā)送這個通知罚攀。這個通知最多發(fā)送一次党觅。當NSThread第一次發(fā)送用`detachNewThreadSelector:toTarget:withObject:`,`detachNewThreadWithBlock:`,`start`消息時,發(fā)送通知斋泄。后續(xù)調(diào)用這些方法是不會發(fā)送通知杯瞻。
NSWillBecomeMultiThreadedNotification

線程間通信,
在NSObject的分類NSThreadPerformAdditions中的方法(NSThread.h文件中)具有這些特性:

  1. 無論是在主線程還是在子線程中都可執(zhí)行炫掐,并且均會調(diào)用主線程的aSelector方法魁莉;
  2. 方法是異步的
@interface NSObject (NSThreadPerformAdditions)
// 如果設(shè)置wait為YES: 等待當前線程執(zhí)行完以后,主線程才會執(zhí)行aSelector方法卒废;
// 如果設(shè)置wait為NO:不等待當前線程執(zhí)行完沛厨,就在主線程上執(zhí)行aSelector方法。
// 如果摔认,當前線程就是主線程逆皮,那么aSelector方法會馬上執(zhí)行,wait是YES參數(shù)無效参袱。
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;

// 等于第一個方法中modes是kCFRunLoopCommonModes的情況电谣。指定了線程中 Runloop 的 Modes =  kCFRunLoopCommonModes。
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

// 在指定線程上操作抹蚀,因為子線程默認未添加NSRunloop剿牺,在線程未添加runloop時,是不會調(diào)用選擇器中的方法的环壤。
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:( NSArray<NSString *> *)array ;
// 等于第一個方法中modes是kCFRunLoopCommonModes的情況晒来。
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait ;

// 隱式創(chuàng)建子線程,在后臺創(chuàng)建。并且是個同步線程郑现。
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg ;
@end

直接給接受者發(fā)消息的其他方法湃崩。

  1. 協(xié)議NSObject中的方法荧降,可在主線程或者子線程執(zhí)行。因為是在當前線程執(zhí)行的同步任務(wù)攒读,因此會阻塞當前線程朵诫。這幾個方法等同于直接調(diào)用方法。
// 當前線程操作薄扁。
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
  1. 延遲操作&按照順序操作

NSRunLoop.h文件中

// 延遲操作
/****************   Delayed perform  ******************/

@interface NSObject (NSDelayedPerforming)
// 異步方法剪返,不會阻塞當前線程,只能在主線程中執(zhí)行邓梅。是把`Selector`加到主隊列里脱盲,當 `delay`之后執(zhí)行`Selector`。如果主線程在執(zhí)行業(yè)務(wù)日缨,那只能等到執(zhí)行完所有業(yè)務(wù)之后才會去執(zhí)行`Selector`宾毒,就算`delay`等于 0。
// 那`delay `從什么時候開始計算呢殿遂?從發(fā)送`performSelector`消息的時候。就算這時主線程在阻塞也會計算時間乙各,當阻塞結(jié)束之后墨礁,如果到了`delay`那就執(zhí)行`Selector`,如果沒到就繼續(xù) `delay`耳峦。
// 只能在主線程中執(zhí)行恩静,在子線程中不會調(diào)到aSelector方法
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
// 等于第一個方法中modes是kCFRunLoopCommonModes的情況。指定了線程中 Runloop 的 Modes =  kCFRunLoopCommonModes蹲坷。
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
// 在方法未到執(zhí)行時間之前驶乾,取消方法。調(diào)用這2個方法當前target執(zhí)行dealloc之前循签,以確保不會Crash级乐。
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;

@end
// 按照排序順序執(zhí)行
@interface NSRunLoop (NSOrderedPerform)
// 按某種順序order執(zhí)行方法。參數(shù)order越小县匠,優(yōu)先級越高风科,執(zhí)行越早
// selector都是target的方法,argument都是target的參數(shù)
// 這2個方法會設(shè)置一個定時器去在下個runloop循環(huán)的開始時讓target執(zhí)行aSelector消息乞旦。 定時器根據(jù)modes確認模式贼穆。當定時器觸發(fā),定時器嘗試隊列從runloop中拿出消息并執(zhí)行兰粉。
如果run loop 正在運行故痊,并且是指定modes的一種,則是成功的玖姑,否則定時器一直等待直到runloop是modes 中的一種愕秫。
- (void)performSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg order:(NSUInteger)order modes:(NSArray<NSRunLoopMode> *)modes;
- (void)cancelPerformSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg;
- (void)cancelPerformSelectorsWithTarget:(id)target;

@end

本文介紹大部分的知識點如思維導(dǎo)圖:


image

使用

  1. 創(chuàng)建線程
    用initXXX初始化的需要調(diào)用start方法來啟動線程慨菱。而detachXXX初始化方法,直接啟動線程豫领。這個2中方式創(chuàng)建的線程都是顯式創(chuàng)建線程抡柿。
//1. 手動開啟,action-target 方式
NSThread * actionTargetThread = [[NSThread alloc] initWithTarget:self selector:@selector(add:) object:nil];
[actionTargetThread start];
//2. 手動開啟等恐, block 方式
NSThread *blockThread = [[NSThread alloc] initWithBlock:^{
    NSLog(@"%s",__func__);
}];
[blockThread start];
//3. 創(chuàng)建就啟動洲劣, action-target 方式
[NSThread detachNewThreadSelector:@selector(add2:) toTarget:self withObject:@"detachNewThreadSelector"];
//4. 創(chuàng)建就啟動, block 方式
[NSThread detachNewThreadWithBlock:^{
    NSLog(@"%s",__func__);
}];
  1. 線程中通信

2.1 NSThreadPerformAdditions分類方法课蔬,異步調(diào)用方法
// 無論在子線程還是主線程囱稽,都會調(diào)用主線程方法。

a. 主線程

    [self performSelectorOnMainThread:@selector(add:) withObject:nil waitUntilDone:YES];
    //[self performSelectorOnMainThread:@selector(add:) withObject:@"arg" waitUntilDone:YES modes:@[(NSRunLoopMode)kCFRunLoopDefaultMode]];

子線程默認沒有開啟runloop二跋。需要手動添加战惊,不然選擇器方法無法調(diào)用。

b. 子線程

使用initWithBlock:方式創(chuàng)建扎即。

//1. 開辟一個子線程
NSThread *subThread1 = [[NSThread alloc] initWithBlock:^{
  // 2.子線程方法中添加runloop
  // 3.實現(xiàn)線程方法
    [[NSRunLoop currentRunLoop] run];
}];
//1.2. 啟動一個子線程
[subThread1 start];
// 2. 在子線程中調(diào)用方法
// [self performSelector:@selector(add:) onThread:subThread1 withObject:@"22" waitUntilDone:YES];
[self performSelector:@selector(add:) onThread:subThread1 withObject:@"arg" waitUntilDone:YES modes:@[(NSRunLoopMode)kCFRunLoopDefaultMode]];

使用initWithTarget:selector:object:創(chuàng)建吞获。

// 1. 開辟一個子線程
NSThread *subThread2 = [[NSThread alloc] initWithTarget:self selector:@selector(startThread) object:nil];
// 1.2 啟動一個子線程
[subThread2 start];
// 3. 在子線程中調(diào)用方法
// [self performSelector:@selector(add:) onThread:subThread2 withObject:@"22" waitUntilDone:YES];
[self performSelector:@selector(add:) onThread:subThread1 withObject:@"arg" waitUntilDone:YES modes:@[(NSRunLoopMode)kCFRunLoopDefaultMode]];
// 2.子線程方法中添加runloop
- (void)startThread{
    [[NSRunLoop currentRunLoop] run];
}

c. 后臺線程(隱式創(chuàng)建一個線程)

[self performSelectorInBackground:@selector(add:) withObject:@"arg"];

2.2 協(xié)議NSObject方法
創(chuàng)建是的同步任務(wù)。

[NSThread detachNewThreadWithBlock:^{
    // 直接調(diào)用
    [self performSelector:@selector(add:) withObject:@"xxx"];
}];

2.3 延遲
NSObject分類NSDelayedPerforming方法谚鄙,添加異步任務(wù)各拷,并且是在主線程上執(zhí)行。

[self performSelector:@selector(add:) withObject:self afterDelay:2];

2.4 按照順序操作
NSRunLoop分類NSOrderedPerform中的方法

[NSThread detachNewThreadWithBlock:^{
    NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];
    // 記得添加端口闷营。不然無法調(diào)用selector方法
    [currentRunloop addPort:[NSPort port] forMode:(NSRunLoopMode)kCFRunLoopCommonModes];
    [currentRunloop performSelector:@selector(add:) target:self argument:@"arg1" order:1 modes:@[(NSRunLoopMode)kCFRunLoopDefaultMode]];
    [currentRunloop performSelector:@selector(add:) target:self argument:@"arg3" order:3 modes:@[(NSRunLoopMode)kCFRunLoopDefaultMode]];
    [currentRunloop run];
}];

線程安全

問題:

多個線程可能會同時訪問同一塊資源烤黍。比如多個線程同時訪問同一個對象、同一個變量傻盟、同一個文件等速蕊。當多個線程同時搶奪同一個資源,會引起線程不安全性娘赴,可能會造成數(shù)據(jù)錯亂和數(shù)據(jù)安全問題规哲。

解決:

使用線程同步技術(shù): 可以對可能會被搶奪的資源,在被被競爭的時候加鎖筝闹。讓其保證線程同步狀態(tài)媳叨。而鎖具有多種類型:比如讀寫鎖、自旋鎖关顷、互斥鎖糊秆、信號量、條件鎖等议双。在NSThread可能造成資源搶奪情況下痘番,可以使用互斥鎖。互斥鎖就是多個線程任務(wù)按順序的執(zhí)行汞舱。
如下就使用的情況之一:對需要讀寫操作的資源伍纫,進行加鎖操作。

for (NSInteger index = 0 ; index < 100; index ++) {
    @synchronized (self) {
        self.allCount -= 5;
        NSLog(@"%@賣出了車票昂芜,還剩%ld",[NSThread currentThread].name,self.allCount);
    }
}

線程生命周期莹规。

線程的生命周期是:新建 - 就緒 - 運行 - 阻塞 - 死亡。當線程啟動后泌神,它不能一直“霸占”著CPU獨自運行良漱,所以CPU需要在多條線程之間切換,于是線程狀態(tài)也就會隨之改變欢际。

image
  1. 新建和就緒狀態(tài)
    ?顯式創(chuàng)建母市,使用initWithTarget:selector:initWithBlock:創(chuàng)建一個線程,未啟動损趋,只有發(fā)送start消息才會啟動患久,然后處于就行狀態(tài)。
    使用detachNewThreadWithBlock:detachNewThreadSelector:toTarget:顯示創(chuàng)建并立即啟動浑槽。 還有種創(chuàng)建方式蒋失,隱式創(chuàng)建并立即啟動:performSelectorInBackground:withObject:

  2. 運行和阻塞狀態(tài)
    如果處于就緒狀態(tài)的線程獲得了CPU資源桐玻,開始執(zhí)行可執(zhí)行方法的線程執(zhí)行體(block或者@Selector)高镐,則該線程處于運行狀態(tài)。

當發(fā)生如下情況下畸冲,線程將會進入阻塞狀態(tài):

  • 線程調(diào)用sleep方法:sleepUntilDate: sleepForTimeInterval:主動放棄所占用的處理器資源。
  • 線程調(diào)用了一個阻塞式IO方法观腊,在該方法返回之前邑闲,該線程被阻塞。
    線程試圖獲得一個同步監(jiān)視器梧油,但該同步監(jiān)視器正被其他線程鎖持有苫耸。
  • 線程在等待某個通知(notify)。
  • 程序調(diào)用了線程的suspend方法將該線程掛起儡陨。不過這個方法容易導(dǎo)致死鎖褪子,所以程序應(yīng)該盡量避免使用該方法。
    ??當前正在執(zhí)行的線程被阻塞之后骗村,其他線程就可以獲得執(zhí)行的機會了嫌褪。被阻塞的線程會在合適時候重新進入就緒狀態(tài),注意是就緒狀態(tài)而不是運行狀態(tài)胚股。也就是
    說被阻塞線程的阻塞解除后笼痛,必須重新等待線程調(diào)度器再次調(diào)度它。
    針對上面的幾種情況,當發(fā)生如下特定的情況將可以解除上面的阻塞缨伊,讓該線程重新進入就緒狀態(tài):
  • 調(diào)用sleep方法的線程經(jīng)過了指定時間摘刑。
  • 線程調(diào)用的阻塞式IO方法已經(jīng)返回。
  • 線程成功地獲得了試圖取得同步監(jiān)視器刻坊。
  • 線程正在等待某個通知時枷恕,其他線程發(fā)出了一個通知。
  • 處于掛起狀態(tài)的線程被調(diào)用了resume恢復(fù)方法谭胚。
  1. 線程死亡
  • 可執(zhí)行方法執(zhí)行完成徐块,線程正常結(jié)束。
  • 程序的意外奔潰漏益。
  • 該線程的發(fā)送exit消息來結(jié)束該線程蛹锰。
// 1. 創(chuàng)建:New狀態(tài)
NSThread * actionTargetThread = [[NSThread alloc] initWithTarget:self selector:@selector(add:) object:nil];
// 2. 啟動:就緒狀態(tài)
[actionTargetThread start];
// 可執(zhí)行方法
- (void)add:(id)info{
    // 3. 執(zhí)行狀態(tài)
    NSLog(@"%s,info %@",__func__,info);
    // 5. 當前線程休眠
    [NSThread sleepForTimeInterval:1.0];
    NSLog(@"after");
    // 4. 程序正常退出
}
// 6. 打取消標簽
[actionTargetThread cancel];
// 7. 主動退出
[NSThread exit];

注意:

  • NSThread 管理多個線程比較困難,所以不太推薦在多線程任務(wù)多的情況下使用。
  • 蘋果官方推薦?使用GCD和NSOperation绰疤。
  • [NSTread currentThread] 跟蹤任務(wù)所在線程,適用于NSTread,NSOperation,和GCD
  • 用NSThread創(chuàng)建的線程,不會自動添加autoreleasepool

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末铜犬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子轻庆,更是在濱河造成了極大的恐慌癣猾,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件余爆,死亡現(xiàn)場離奇詭異纷宇,居然都是意外死亡,警方通過查閱死者的電腦和手機蛾方,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門像捶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人桩砰,你說我怎么就攤上這事拓春。” “怎么了亚隅?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵硼莽,是天一觀的道長。 經(jīng)常有香客問我煮纵,道長懂鸵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任行疏,我火速辦了婚禮匆光,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘酿联。我一直安慰自己殴穴,他們只是感情好,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著采幌,像睡著了一般劲够。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上休傍,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天征绎,我揣著相機與錄音,去河邊找鬼磨取。 笑死人柿,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的忙厌。 我是一名探鬼主播凫岖,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼逢净!你這毒婦竟也來了哥放?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤爹土,失蹤者是張志新(化名)和其女友劉穎甥雕,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胀茵,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡社露,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了琼娘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片峭弟。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖脱拼,靈堂內(nèi)的尸體忽然破棺而出孟害,到底是詐尸還是另有隱情,我是刑警寧澤挪拟,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站击你,受9級特大地震影響玉组,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜丁侄,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一惯雳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鸿摇,春花似錦石景、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽揪荣。三九已至,卻和暖如春往史,著一層夾襖步出監(jiān)牢的瞬間仗颈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工椎例, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留挨决,地道東北人。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓订歪,卻偏偏與公主長得像脖祈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子刷晋,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容