iOS多線程總結

進程

  • 什么是進程
  • 進程是指在系統中正在運行的一個應用程序
  • 每個進程之間是獨立的,每個進程均運行在其專用且受保護的內存空間內
  • 比如同時打開迅雷、Xcode,系統就會分別啟動2個進程
  • 通過“活動監視器”可以查看Mac系統中所開啟的進程

線程

  • 什么是線程
  • 1個進程要想執行任務,必須得有線程(每1個進程至少要有1條線程)
  • 一個進程(程序)的所有任務都在線程中執行
  • 比如使用酷狗播放音樂、使用迅雷下載電影,都需要在線程中執行

線程串行

  • 1個線程中任務的執行是串行的
  • 如果要在1個線程中執行多個任務,那么只能一個一個地按順序執行這些任務
  • 也就是說,在同一時間內,1個線程只能執行1個任務

多線程

  • 什么是多線程
  • 1個進程中可以開啟多條線程,每條線程可以并行(同時)執行不同的任務
  • 進程 車間,線程 車間工人
  • 多線程技術可以提高程序的執行效率

多線程的優缺點

  • 多線程的優點
  • 能適當提高程序的執行效率
  • 能適當提高資源利用率(CPU、內存利用率)
  • 多線程的缺點
  • 建線程是有開銷的,iOS下主要成本包括:內核數據結構(大約1KB)、棧空間(子線程512KB、主線程1MB,也可以使用-setStackSize:設置,但必須是4K的倍數,而且最小是16K),創建線程大約需要90毫秒的創建時間
  • 如果開啟大量的線程,會降低程序的性能
  • 線程越多,CPU在調度線程上的開銷就越大
  • 程序設計更加復雜:比如線程之間的通信、多線程的數據共享

多線程在iOS開發中的應用

  • 什么是主線程
  • 一個iOS程序運行后,默認會開啟1條線程,稱為“主線程”或“UI線程”
  • 主線程的主要作用
  • 顯示\刷新UI界面
  • 處理UI事件(比如點擊事件、滾動事件、拖拽事件等)
  • 主線程的使用注意
  • 別將比較耗時的操作放到主線程中
  • 耗時操作會卡住主線程,嚴重影響UI的流暢度,給用戶一種“卡”的壞體驗

NSThread

創建和啟動線程

  • 一個NSThread對象就代表一條線程
  • 創建、啟動線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];
// 線程一啟動,就會在線程thread中執行self的run方法
  • 主線程相關用法
+(NSThread *)mainThread; // 獲得主線程
-(BOOL)isMainThread; // 是否為主線程
+(BOOL)isMainThread; // 是否為主線程
  • 其他用法
  • 獲得當前線程
NSThread *current = [NSThread currentThread];
  • 線程的名字
-(void)setName:(NSString *)n;
-(NSString *)name;
  • 其他創建線程方式
  • 創建線程后自動啟動線程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
  • 隱式創建并啟動線程
[self performSelectorInBackground:@selector(run) withObject:nil];
  • 上述2種創建線程方式的優缺點
    • 優點:簡單快捷
    • 缺點:無法對線程進行更詳細的設置

控制線程狀態

  • 啟動線程
-(void)start; 
// 進入就緒狀態 -> 運行狀態。當線程任務執行完畢,自動進入死亡狀態
  • 阻塞(暫停)線程
+(void)sleepUntilDate:(NSDate *)date;
+(void)sleepForTimeInterval:(NSTimeInterval)ti;
// 進入阻塞狀態
  • 強制停止線程
+(void)exit;
// 進入死亡狀態

多線程的安全隱患

  • 資源共享
  • 1塊資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊資源
  • 比如多個線程訪問同一個對象、同一個變量、同一個文件
  • 當多個線程訪問同一塊資源時,很容易引發數據錯亂和數據安全問題
  • 安全隱患解決 – 互斥鎖
  • 互斥鎖使用格式
@synchronized(鎖對象) { // 需要鎖定的代碼  }
//注意:鎖定1份代碼只用1把鎖,用多把鎖是無效的
  • 互斥鎖的優缺點
    • 優點:能有效防止因多線程搶奪資源造成的數據安全問題
    • 缺點:需要消耗大量的CPU資源
  • 互斥鎖的使用前提:多條線程搶奪同一塊資源
  • 相關專業術語:線程同步
    • 線程同步的意思是:多條線程在同一條線上執行(按順序地執行任務)
    • 互斥鎖,就是使用了線程同步技術

原子和非原子屬性

  • OC在定義屬性時有nonatomicatomic兩種選擇
  • atomic:原子屬性,為setter方法加鎖(默認就是atomic)
  • nonatomic:非原子屬性,不會為setter方法加鎖
  • nonatomicatomic對比
  • atomic:線程安全,需要消耗大量的資源
  • nonatomic:非線程安全,適合內存小的移動設備
  • iOS開發的建議
  • 所有屬性都聲明為nonatomic
  • 盡量避免多線程搶奪同一塊資源
  • 盡量將加鎖、資源搶奪的業務邏輯交給服務器端處理,減小移動客戶端的壓力

線程間通信

  • 什么叫做線程間通信
  • 在1個進程中,線程往往不是孤立存在的,多個線程之間需要經常進行通信
  • 線程間通信的體現
  • 1個線程傳遞數據給另1個線程
  • 在1個線程中執行完特定任務后,轉到另1個線程繼續執行任務
  • 線程間通信常用方法
-(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
-(void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
  • 線程間通信方式 – 利用NSPort(端口類很少用)

GCD

簡介

  • 什么是GCD
  • 全稱是Grand Central Dispatch,可譯為“牛逼的中樞調度器”
  • 純C語言,提供了非常多強大的函數
  • GCD的優勢
  • GCD是蘋果公司為多核的并行運算提出的解決方案
  • GCD會自動利用更多的CPU內核(比如雙核、四核)
  • GCD會自動管理線程的生命周期(創建線程、調度任務、銷毀線程)
  • 程序員只需要告訴GCD想要執行什么任務,不需要編寫任何線程管理代碼

任務和隊列

  • GCD中有2個核心概念
  • 任務:執行什么操作
  • 隊列:用來存放任務
  • GCD的使用就2個步驟
  • 定制任務
    • 確定想做的事情
  • 將任務添加到隊列中
    • GCD會自動將隊列中的任務取出,放到對應的線程中執行
    • 任務的取出遵循隊列的FIFO原則:先進先出,后進后出

執行任務

  • GCD中有2個用來執行任務的常用函數
  • 用同步的方式執行任務
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
//queue:隊列
//block:任務
  • 用異步的方式執行任務
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
  • 同步和異步的區別
  • 同步:只能在當前線程中執行任務,不具備開啟新線程的能力
  • 異步:可以在新的線程中執行任務,具備開啟新線程的能力
  • GCD中還有個用來執行任務的函數:
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
//在前面的任務執行結束后它才執行,而且它后面的任務等它執行完成之后才會執行

隊列的類型

  • GCD的隊列可以分為2大類型
  • 并發隊列(Concurrent Dispatch Queue)
    • 可以讓多個任務并發(同時)執行(自動開啟多個線程同時執行任務)
    • 并發功能只有在異步(dispatch_async)函數下才有效
  • 串行隊列(Serial Dispatch Queue)
    • 讓任務一個接著一個地執行(一個任務執行完畢后,再執行下一個任務)

容易混淆的術語

  • 有4個術語比較容易混淆:同步、異步、并發、串行
  • 同步和異步主要影響:能不能開啟新的線程
  • 同步:只是在當前線程中執行任務,不具備開啟新線程的能力
  • 異步:可以在新的線程中執行任務,具備開啟新線程的能力
  • 并發和串行主要影響:任務的執行方式
  • 并發:多個任務并發(同時)執行
  • 串行:一個任務執行完畢后,再執行下一個任務

并發隊列

  • 使用dispatch_queue_create函數創建隊列
dispatch_queue_t
dispatch_queue_create(const char *label, // 隊列名稱 
dispatch_queue_attr_t attr); // 隊列的類型
  • 創建并發隊列
dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_CONCURRENT);
  • GCD默認已經提供了全局的并發隊列,供整個應用使用,可以無需手動創建
  • 使用dispatch_get_global_queue函數獲得全局的并發隊列
dispatch_queue_t dispatch_get_global_queue(
dispatch_queue_priority_t priority, // 隊列的優先級
unsigned long flags); // 此參數暫時無用,用0即可
  • 獲得全局并發隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
  • 全局并發隊列的優先級
  • #define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
  • #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認(中)
  • #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
  • #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺

串行隊列

  • GCD中獲得串行有2種途徑
  • 使用dispatch_queue_create函數創建串行隊列
// 創建串行隊列(隊列類型傳遞NULL或者DISPATCH_QUEUE_SERIAL)
dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", NULL); 
  • 使用主隊列(跟主線程相關聯的隊列)
    • 主隊列是GCD自帶的一種特殊的串行隊列
    • 放在主隊列中的任務,都會放到主線程中執行
    • 使用dispatch_get_main_queue()獲得主隊列
      dispatch_queue_t queue = dispatch_get_main_queue();

各種隊列的執行效果

/**
 * 同步函數+主隊列 線程堵死
 */
-(void)syncMain{
    
    NSLog(@"begin");
    //1創建串行隊列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_sync(queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2---%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3---%@",[NSThread currentThread]);
    });
    NSLog(@"end");

}
/**
 * 異步函數+主隊列 只在主線程中執行任務
 */
-(void)asyncMain{
    //1創建串行隊列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2---%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3---%@",[NSThread currentThread]);
    });

}
/**
 * 同步函數+串行隊列 不會開啟新線程
 */
-(void)syncSerial{

    dispatch_queue_t queue = dispatch_queue_create("com.520.queue", DISPATCH_QUEUE_SERIAL);

    dispatch_sync(queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2---%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3---%@",[NSThread currentThread]);
    });

}
/**
 * 異步函數+串行隊列 會開啟新的線程,但是任務是串行,執行完一個任務在執行下一個任務
 */
-(void)asyncSerial{
    
    //1創建串行隊列
    dispatch_queue_t queue = dispatch_queue_create("com.520.queue", DISPATCH_QUEUE_SERIAL);

    dispatch_async(queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2---%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3---%@",[NSThread currentThread]);
    });
}
/**
 * 同步函數+并行隊列 不會開啟新線程
 */
-(void)syncConcurrent{
    //1獲得全局的并發隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_sync(queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2---%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3---%@",[NSThread currentThread]);
    });
}
/**
 * 異步函數+并行隊列 可以同時開啟多條線程
 */
-(void)asyncConcurrent{

    //1創建一個并發隊列
//    dispatch_queue_t queue = dispatch_queue_create("com.520.queue", DISPATCH_QUEUE_CONCURRENT);
    
    //1獲得全局的并發隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2---%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3---%@",[NSThread currentThread]);
    });
}

線程間通信示例

  • 從子線程回到主線程
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 執行耗時的異步操作...
      dispatch_async(dispatch_get_main_queue(), ^{
        // 回到主線程,執行UI刷新操作
        });
});

延時執行

  • iOS常見的延時執行
  • 調用NSObject的方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// 2秒后再調用self的run方法
  • 使用GCD函數
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 2秒后異步執行這里的代碼...
});
  • 使用NSTimer
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:NO];

一次性代碼

  • 使用dispatch_once函數能保證某段代碼在程序運行過程中只被執行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只執行1次的代碼(這里面默認是線程安全的)
});

定時器

// 創建Timer
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
// 設置定時器的觸發時間(1秒后)和時間間隔(每隔2秒)
dispatch_source_set_timer(self.timer, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), 2 * NSEC_PER_SEC, 0);
// 設置回調
dispatch_source_set_event_handler(self.timer, ^{
    NSLog(@"Timer %@", [NSThread currentThread]);
});
// 開始定時器
dispatch_resume(self.timer);
//取消定時器
dispatch_cancel(self.timer);
self.timer = nil;
  • 類似于登錄注冊獲取驗證碼的倒計時按鈕
//
//  UIButton+countDown.m
//  計時器按鈕
//
//  Created by 莊子豪 on 16/6/24.
//  Copyright ? 2016年 zzh. All rights reserved.
//

#import "UIButton+countDown.h"

@implementation UIButton (countDown)
- (void)startWithTime:(NSInteger)timeLine title:(NSString *)title countDownTitle:(NSString *)subTitle mainColor:(UIColor *)mColor countColor:(UIColor *)color {
   
    //倒計時時間
    __block NSInteger timeOut = timeLine;
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    //每秒執行一次
    dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 1.0 * NSEC_PER_SEC, 0);
    dispatch_source_set_event_handler(_timer, ^{
       
        //倒計時結束,關閉
        if (timeOut <= 0) {
            dispatch_source_cancel(_timer);
            dispatch_async(dispatch_get_main_queue(), ^{
                self.backgroundColor = mColor;
                [self setTitle:title forState:UIControlStateNormal];
                self.userInteractionEnabled = YES;
            });
        } else {
            int seconds = timeOut % 60;
            NSString *timeStr = [NSString stringWithFormat:@"%0.2d", seconds];
            dispatch_async(dispatch_get_main_queue(), ^{
                self.backgroundColor = color;
                [self setTitle:[NSString stringWithFormat:@"%@%@",timeStr,subTitle] forState:UIControlStateNormal];
                self.userInteractionEnabled = NO;
            });
            timeOut--;
        }
    });
    dispatch_resume(_timer);
}
@end

快速迭代

  • 使用dispatch_apply函數能進行快速迭代遍歷
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index){
    // 執行10次代碼,index順序不確定
});

隊列組

  • 有這么1種需求
  • 首先:分別異步執行2個耗時的操作
  • 其次:等2個異步操作都執行完畢后,再回到主線程執行操作
  • 如果想要快速高效地實現上述需求,可以考慮用隊列組
dispatch_group_t group =  dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 執行1個耗時的異步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 執行1個耗時的異步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 等前面的異步操作都執行完畢后,回到主線程...
});

單例模式

  • 單例模式的作用
  • 可以保證在程序運行過程,一個類只有一個實例,而且該實例易于供外界訪問
  • 從而方便地控制了實例個數,并節約系統資源
  • 單例模式的使用場合
  • 在整個應用程序中,共享一份資源(這份資源只需要創建初始化1次)
  • ARC中,單例模式的實現
  • 在.m中保留一個全局的static的實例static id _instance;
  • 重寫allocWithZone:方法,在這里創建唯一的實例(注意線程安全)
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [super allocWithZone:zone];
    });
    return _instance;
}
  • 提供1個類方法讓外界訪問唯一的實例
+(instancetype)sharedInstance
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[self alloc] init];
    });
    return _instance;
}
  • 實現copyWithZone:方法
-(id)copyWithZone:(struct _NSZone *)zone
{
    return _instance;
}

NSOperation

簡介

  • NSOperation的作用

  • 配合使用NSOperationNSOperationQueue也能實現多線程編程

  • NSOperationNSOperationQueue實現多線程的具體步驟

  • 先將需要執行的操作封裝到一個NSOperation對象中

  • 然后將NSOperation對象添加到NSOperationQueue

  • 系統會自動將NSOperationQueue中的NSOperation取出來

  • 將取出的NSOperation封裝的操作放到一條新線程中執行

NSOperation的子類

  • NSOperation是個抽象類,并不具備封裝操作的能力,必須使用它的子類

  • 使用NSOperation子類的方式有3種

  • NSInvocationOperation

  • NSBlockOperation

  • 自定義子類繼承NSOperation,實現內部相應的方法

NSInvocationOperation

  • 創建NSInvocationOperation對象
-(id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
  • 調用start方法開始執行操作
-(void)start;
//一旦執行操作,就會調用target的sel方法
  • 注意
  • 默認情況下,調用了start方法后并不會開一條新線程去執行操作,而是在當前線程同步執行操作
  • 只有將NSOperation放到一個NSOperationQueue中,才會異步執行操作

NSBlockOperation

  • 創建NSBlockOperation對象
+(id)blockOperationWithBlock:(void (^)(void))block;
  • 通過addExecutionBlock:方法添加更多的操作
-(void)addExecutionBlock:(void (^)(void))block;
  • 注意:只要NSBlockOperation封裝的操作數 > 1,就會異步執行操作

NSOperationQueue

  • NSOperationQueue的作用

  • NSOperation可以調用start方法來執行任務,但默認是同步執行的

  • 如果將NSOperation添加到NSOperationQueue(操作隊列)中,系統會自動異步執行NSOperation中的操作

  • 添加操作到NSOperationQueue

-(void)addOperation:(NSOperation *)op;
-(void)addOperationWithBlock:(void (^)(void))block;

最大并發數

  • 什么是并發數

  • 同時執行的任務數

  • 比如,同時開3個線程執行3個任務,并發數就是3

  • 最大并發數的相關方法

-(NSInteger)maxConcurrentOperationCount;
-(void)setMaxConcurrentOperationCount:(NSInteger)cnt;

隊列的取消、暫停、恢復

  • 取消隊列的所有操作
-(void)cancelAllOperations;
//提示:也可以調用NSOperation的- (void)cancel方法取消單個操作
  • 暫停和恢復隊列
-(void)setSuspended:(BOOL)b; // YES代表暫停隊列,NO代表恢復隊列
-(BOOL)isSuspended;

操作依賴

  • NSOperation之間可以設置依賴來保證執行順序
  • 比如一定要讓操作A執行完后,才能執行操作B,可以這么寫
[operationB addDependency:operationA]; // 操作B依賴于操作A
  • 可以在不同queue的NSOperation之間創建依賴關系

操作的監聽

  • 可以監聽一個操作的執行完畢
-(void (^)(void))completionBlock;
-(void)setCompletionBlock:(void (^)(void))block;

自定義NSOperation

  • 自定義NSOperation的步驟很簡單

  • 重寫- (void)main方法,在里面實現想執行的任務

  • 重寫- (void)main方法的注意點

  • 自己創建自動釋放池(因為如果是異步操作,無法訪問主線程的自動釋放池)

  • 經常通過- (BOOL)isCancelled方法檢測操作是否被取消,對取消做出響應

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,048評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,414評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,169評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,722評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,465評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,823評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,813評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,000評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,554評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,295評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,513評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,722評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,125評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,430評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,237評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,482評論 2 379

推薦閱讀更多精彩內容