偽共享,并發(fā)編程無(wú)聲的性能殺手

原文:https://mp.weixin.qq.com/s/LXt24f9Qw2fAQtyPNg-a9A

在并發(fā)編程過(guò)程中,我們大部分的焦點(diǎn)都放在如何控制共享變量的訪問(wèn)控制上(代碼層面)盯另,但是很少人會(huì)關(guān)注系統(tǒng)硬件及 JVM 底層相關(guān)的影響因素蛙吏。前段時(shí)間學(xué)習(xí)了一個(gè)牛X的高性能異步處理框架 Disruptor,它被譽(yù)為“最快的消息框架”稽物,其 LMAX 架構(gòu)能夠在一個(gè)線程里每秒處理 6百萬(wàn) 訂單奄毡!在講到 Disruptor 為什么這么快時(shí),接觸到了一個(gè)概念——偽共享( false sharing )贝或,其中提到:緩存行上的寫(xiě)競(jìng)爭(zhēng)是運(yùn)行在 SMP 系統(tǒng)中并行線程實(shí)現(xiàn)可伸縮性最重要的限制因素吼过。由于從代碼中很難看出是否會(huì)出現(xiàn)偽共享,有人將其描述成無(wú)聲的性能殺手咪奖。

偽共享的非標(biāo)準(zhǔn)定義為:緩存系統(tǒng)中是以緩存行(cache line)為單位存儲(chǔ)的盗忱,當(dāng)多線程修改互相獨(dú)立的變量時(shí),如果這些變量共享同一個(gè)緩存行赡艰,就會(huì)無(wú)意中影響彼此的性能售淡,這就是偽共享。

一慷垮、CPU 緩存

CPU 緩存(Cache Memory)是位于 CPU 與內(nèi)存之間的臨時(shí)存儲(chǔ)器揖闸,它的容量比內(nèi)存小的多但是交換速度卻比內(nèi)存要快得多。

按照數(shù)據(jù)讀取順序和與 CPU 結(jié)合的緊密程度料身,CPU 緩存可以分為一級(jí)緩存汤纸,二級(jí)緩存,部分高端 CPU 還具有三級(jí)緩存芹血。每一級(jí)緩存中所儲(chǔ)存的全部數(shù)據(jù)都是下一級(jí)緩存的一部分贮泞,越靠近 CPU 的緩存越快也越小。所以 L1 緩存很小但很快(譯注:L1 表示一級(jí)緩存)幔烛,并且緊靠著在使用它的 CPU 內(nèi)核啃擦。L2 大一些,也慢一些饿悬,并且仍然只能被一個(gè)單獨(dú)的 CPU 核使用令蛉。L3 在現(xiàn)代多核機(jī)器中更普遍,仍然更大,更慢珠叔,并且被單個(gè)插槽上的所有 CPU 核共享蝎宇。最后,你擁有一塊主存祷安,由全部插槽上的所有 CPU 核共享姥芥。擁有三級(jí)緩存的的 CPU,到三級(jí)緩存時(shí)能夠達(dá)到 95% 的命中率汇鞭,只有不到 5% 的數(shù)據(jù)需要從內(nèi)存中查詢凉唐。

多核機(jī)器的存儲(chǔ)結(jié)構(gòu)如下圖所示:


圖片發(fā)自簡(jiǎn)書(shū)App
二、MESI 協(xié)議及 RFO 請(qǐng)求

從上一節(jié)中我們知道虱咧,每個(gè)核都有自己私有的 L1,熊榛、L2 緩存。那么多線程編程時(shí), 另外一個(gè)核的線程想要訪問(wèn)當(dāng)前核內(nèi) L1腕巡、L2 緩存行的數(shù)據(jù), 該怎么辦呢玄坦?

有人說(shuō)可以通過(guò)第 2 個(gè)核直接訪問(wèn)第 1 個(gè)核的緩存行,這是當(dāng)然是可行的绘沉,但這種方法不夠快煎楣。跨核訪問(wèn)需要通過(guò) Memory Controller(內(nèi)存控制器车伞,是計(jì)算機(jī)系統(tǒng)內(nèi)部控制內(nèi)存并且通過(guò)內(nèi)存控制器使內(nèi)存與 CPU 之間交換數(shù)據(jù)的重要組成部分)择懂,典型的情況是第 2 個(gè)核經(jīng)常訪問(wèn)第 1 個(gè)核的這條數(shù)據(jù),那么每次都有跨核的消耗.另玖。更糟的情況是困曙,有可能第 2 個(gè)核與第 1 個(gè)核不在一個(gè)插槽內(nèi),況且 Memory Controller 的總線帶寬是有限的谦去,扛不住這么多數(shù)據(jù)傳輸慷丽。所以,CPU 設(shè)計(jì)者們更偏向于另一種辦法: 如果第 2 個(gè)核需要這份數(shù)據(jù)鳄哭,由第 1 個(gè)核直接把數(shù)據(jù)內(nèi)容發(fā)過(guò)去要糊,數(shù)據(jù)只需要傳一次。

那么什么時(shí)候會(huì)發(fā)生緩存行的傳輸呢妆丘?答案很簡(jiǎn)單:當(dāng)一個(gè)核需要讀取另外一個(gè)核的臟緩存行時(shí)發(fā)生锄俄。但是前者怎么判斷后者的緩存行已經(jīng)被弄臟(寫(xiě))了呢?

下面將詳細(xì)地解答以上問(wèn)題. 首先我們需要談到一個(gè)協(xié)議—— MESI 協(xié)議∩准穑現(xiàn)在主流的處理器都是用它來(lái)保證緩存的相干性和內(nèi)存的相干性奶赠。M、E药有、S 和 I 代表使用 MESI 協(xié)議時(shí)緩存行所處的四個(gè)狀態(tài):

  • M(修改车柠,Modified):本地處理器已經(jīng)修改緩存行,即是臟行,它的內(nèi)容與內(nèi)存中的內(nèi)容不一樣竹祷,并且此 cache 只有本地一個(gè)拷貝(專有);

  • E(專有羊苟,Exclusive):緩存行內(nèi)容和內(nèi)存中的一樣塑陵,而且其它處理器都沒(méi)有這行數(shù)據(jù);

  • S(共享蜡励,Shared):緩存行內(nèi)容和內(nèi)存中的一樣, 有可能其它處理器也存在此緩存行的拷貝令花;

  • I(無(wú)效,Invalid):緩存行失效, 不能使用凉倚。

下面說(shuō)明這四個(gè)狀態(tài)是如何轉(zhuǎn)換的:

初始:一開(kāi)始時(shí)兼都,緩存行沒(méi)有加載任何數(shù)據(jù),所以它處于 I 狀態(tài)稽寒。

本地寫(xiě)(Local Write):如果本地處理器寫(xiě)數(shù)據(jù)至處于 I 狀態(tài)的緩存行扮碧,則緩存行的狀態(tài)變成 M。

本地讀(Local Read):如果本地處理器讀取處于 I 狀態(tài)的緩存行杏糙,很明顯此緩存沒(méi)有數(shù)據(jù)給它慎王。此時(shí)分兩種情況:(1)其它處理器的緩存里也沒(méi)有此行數(shù)據(jù),則從內(nèi)存加載數(shù)據(jù)到此緩存行后宏侍,再將它設(shè)成 E 狀態(tài)赖淤,表示只有我一家有這條數(shù)據(jù),其它處理器都沒(méi)有谅河;(2)其它處理器的緩存有此行數(shù)據(jù)咱旱,則將此緩存行的狀態(tài)設(shè)為 S 狀態(tài)。(備注:如果處于M狀態(tài)的緩存行绷耍,再由本地處理器寫(xiě)入/讀出吐限,狀態(tài)是不會(huì)改變的)

遠(yuǎn)程讀(Remote Read):假設(shè)我們有兩個(gè)處理器 c1 和 c2,如果 c2 需要讀另外一個(gè)處理器 c1 的緩存行內(nèi)容锨天,c1 需要把它緩存行的內(nèi)容通過(guò)內(nèi)存控制器 (Memory Controller) 發(fā)送給 c2毯盈,c2 接到后將相應(yīng)的緩存行狀態(tài)設(shè)為 S。在設(shè)置之前病袄,內(nèi)存也得從總線上得到這份數(shù)據(jù)并保存搂赋。

遠(yuǎn)程寫(xiě)(Remote Write):其實(shí)確切地說(shuō)不是遠(yuǎn)程寫(xiě),而是 c2 得到 c1 的數(shù)據(jù)后益缠,不是為了讀脑奠,而是為了寫(xiě)。也算是本地寫(xiě)幅慌,只是 c1 也擁有這份數(shù)據(jù)的拷貝宋欺,這該怎么辦呢?c2 將發(fā)出一個(gè) RFO (Request For Owner) 請(qǐng)求,它需要擁有這行數(shù)據(jù)的權(quán)限齿诞,其它處理器的相應(yīng)緩存行設(shè)為 I酸休,除了它自已,誰(shuí)不能動(dòng)這行數(shù)據(jù)祷杈。這保證了數(shù)據(jù)的安全斑司,同時(shí)處理 RFO 請(qǐng)求以及設(shè)置I的過(guò)程將給寫(xiě)操作帶來(lái)很大的性能消耗。

狀態(tài)轉(zhuǎn)換由下圖做個(gè)補(bǔ)充:


圖片發(fā)自簡(jiǎn)書(shū)App

我們從上節(jié)知道但汞,寫(xiě)操作的代價(jià)很高宿刮,特別當(dāng)需要發(fā)送 RFO 消息時(shí)。我們編寫(xiě)程序時(shí)私蕾,什么時(shí)候會(huì)發(fā)生 RFO 請(qǐng)求呢僵缺?有以下兩種:

  1. 線程的工作從一個(gè)處理器移到另一個(gè)處理器, 它操作的所有緩存行都需要移到新的處理器上。此后如果再寫(xiě)緩存行踩叭,則此緩存行在不同核上有多個(gè)拷貝磕潮,需要發(fā)送 RFO 請(qǐng)求了。

  2. 兩個(gè)不同的處理器確實(shí)都需要操作相同的緩存行

接下來(lái)懊纳,我們要了解什么是緩存行揉抵。

三、緩存行

在文章開(kāi)頭提到過(guò)嗤疯,緩存系統(tǒng)中是以緩存行(cache line)為單位存儲(chǔ)的冤今。緩存行通常是 64 字節(jié)(譯注:本文基于 64 字節(jié),其他長(zhǎng)度的如 32 字節(jié)等不適本文討論的重點(diǎn))茂缚,并且它有效地引用主內(nèi)存中的一塊地址戏罢。一個(gè) Java 的 long 類型是 8 字節(jié),因此在一個(gè)緩存行中可以存 8 個(gè) long 類型的變量脚囊。所以龟糕,如果你訪問(wèn)一個(gè) long 數(shù)組,當(dāng)數(shù)組中的一個(gè)值被加載到緩存中悔耘,它會(huì)額外加載另外 7 個(gè)讲岁,以致你能非常快地遍歷這個(gè)數(shù)組衬以。事實(shí)上缓艳,你可以非常快速的遍歷在連續(xù)的內(nèi)存塊中分配的任意數(shù)據(jù)結(jié)構(gòu)看峻。而如果你在數(shù)據(jù)結(jié)構(gòu)中的項(xiàng)在內(nèi)存中不是彼此相鄰的(如鏈表)阶淘,你將得不到免費(fèi)緩存加載所帶來(lái)的優(yōu)勢(shì),并且在這些數(shù)據(jù)結(jié)構(gòu)中的每一個(gè)項(xiàng)都可能會(huì)出現(xiàn)緩存未命中互妓。

如果存在這樣的場(chǎng)景溪窒,有多個(gè)線程操作不同的成員變量坤塞,但是相同的緩存行,這個(gè)時(shí)候會(huì)發(fā)生什么澈蚌?摹芙。沒(méi)錯(cuò),偽共享(False Sharing)問(wèn)題就發(fā)生了惜浅!有張 Disruptor 項(xiàng)目的經(jīng)典示例圖瘫辩,如下:

圖片發(fā)自簡(jiǎn)書(shū)App

上圖中,一個(gè)運(yùn)行在處理器 core1上的線程想要更新變量 X 的值坛悉,同時(shí)另外一個(gè)運(yùn)行在處理器 core2 上的線程想要更新變量 Y 的值。但是承绸,這兩個(gè)頻繁改動(dòng)的變量都處于同一條緩存行裸影。兩個(gè)線程就會(huì)輪番發(fā)送 RFO 消息,占得此緩存行的擁有權(quán)军熏。當(dāng) core1 取得了擁有權(quán)開(kāi)始更新 X轩猩,則 core2 對(duì)應(yīng)的緩存行需要設(shè)為 I 狀態(tài)。當(dāng) core2 取得了擁有權(quán)開(kāi)始更新 Y荡澎,則 core1 對(duì)應(yīng)的緩存行需要設(shè)為 I 狀態(tài)(失效態(tài))均践。輪番奪取擁有權(quán)不但帶來(lái)大量的 RFO 消息,而且如果某個(gè)線程需要讀此行數(shù)據(jù)時(shí)摩幔,L1 和 L2 緩存上都是失效數(shù)據(jù)彤委,只有 L3 緩存上是同步好的數(shù)據(jù)。從前一篇我們知道或衡,讀 L3 的數(shù)據(jù)非常影響性能焦影。更壞的情況是跨槽讀取,L3 都要 miss封断,只能從內(nèi)存上加載斯辰。

表面上 X 和 Y 都是被獨(dú)立線程操作的,而且兩操作之間也沒(méi)有任何關(guān)系坡疼。只不過(guò)它們共享了一個(gè)緩存行彬呻,但所有競(jìng)爭(zhēng)沖突都是來(lái)源于共享。

四柄瑰、遭遇偽共享

好的闸氮,那么接下來(lái)我們就用 code 來(lái)進(jìn)行實(shí)驗(yàn)和佐證。

public class FalseShareTest implements Runnable {

    public static int NUM_THREADS = 4;

    public final static long ITERATIONS = 500L * 1000L * 1000L;

    private final int arrayIndex;

    private static VolatileLong[] longs;

    public static long SUM_TIME = 0l;

    public FalseShareTest(final int arrayIndex) {

        this.arrayIndex = arrayIndex;

    }

    public static void main(final String[] args) throws Exception {

        Thread.sleep(10000);

        for(int j=0; j<10; j++){

            System.out.println(j);

            if (args.length == 1) {

                NUM_THREADS = Integer.parseInt(args[0]);

            }

            longs = new VolatileLong[NUM_THREADS];

            for (int i = 0; i < longs.length; i++) {

                longs[i] = new VolatileLong();

            }

            final long start = System.nanoTime();

            runTest();

            final long end = System.nanoTime();

            SUM_TIME += end - start;

        }

        System.out.println("平均耗時(shí):"+SUM_TIME/10);

    }

    private static void runTest() throws InterruptedException {

        Thread[] threads = new Thread[NUM_THREADS];

        for (int i = 0; i < threads.length; i++) {

            threads[i] = new Thread(new FalseShareTest(i));

        }

        for (Thread t : threads) {

            t.start();

        }

        for (Thread t : threads) {

            t.join();

        }

    }

    public void run() {

        long i = ITERATIONS + 1;

        while (0 != --i) {

            longs[arrayIndex].value = i;

        }

    }

    public final static class VolatileLong {

        public volatile long value = 0L;

        public long p1, p2, p3, p4, p5, p6;     //屏蔽此行

    }

}

上述代碼的邏輯很簡(jiǎn)單狱意,就是四個(gè)線程修改一數(shù)組不同元素的內(nèi)容湖苞。元素的類型是 VolatileLong,只有一個(gè)長(zhǎng)整型成員 value 和 6 個(gè)沒(méi)用到的長(zhǎng)整型成員详囤。value 設(shè)為 volatile 是為了讓 value 的修改對(duì)所有線程都可見(jiàn)财骨。程序分兩種情況執(zhí)行镐作,第一種情況為不屏蔽倒數(shù)第三行(見(jiàn)”屏蔽此行”字樣),第二種情況為屏蔽倒數(shù)第三行隆箩。為了”保證”數(shù)據(jù)的相對(duì)可靠性该贾,程序取 10 次執(zhí)行的平均時(shí)間。執(zhí)行情況如下(執(zhí)行環(huán)境:32位 windows捌臊,四核杨蛋,8GB 內(nèi)存):

不屏蔽
屏蔽

兩個(gè)邏輯一模一樣的程序,前者的耗時(shí)大概是后者的 2.5 倍理澎,這太不可思議了逞力!那么這個(gè)時(shí)候,我們?cè)儆脗喂蚕恚‵alse Sharing)的理論來(lái)分析一下糠爬。前者 longs 數(shù)組的 4 個(gè)元素寇荧,由于 VolatileLong 只有 1 個(gè)長(zhǎng)整型成員,所以整個(gè)數(shù)組都將被加載至同一緩存行执隧,但有4個(gè)線程同時(shí)操作這條緩存行揩抡,于是偽共享就悄悄地發(fā)生了。

基于此镀琉,我們有理由相信峦嗤,在一定線程數(shù)量范圍內(nèi)(注意思考:為什么強(qiáng)調(diào)是一定線程數(shù)量范圍內(nèi)),隨著線程數(shù)量的增加屋摔,偽共享發(fā)生的頻率也越大烁设,直觀體現(xiàn)就是執(zhí)行時(shí)間越長(zhǎng)。為了證實(shí)這個(gè)觀點(diǎn)凡壤,本人在同樣的機(jī)器上分別用單線程署尤、2、4亚侠、8個(gè)線程曹体,對(duì)有填充和無(wú)填充兩種情況進(jìn)行測(cè)試。執(zhí)行場(chǎng)景是取 10 次執(zhí)行的平均時(shí)間硝烂,結(jié)果如下所示:

image.png
五箕别、如何避免偽共享

其中一個(gè)解決思路,就是讓不同線程操作的對(duì)象處于不同的緩存行即可滞谢。

那么該如何做到呢串稀?其實(shí)在我們注釋的那行代碼中就有答案,那就是緩存行填充(Padding) ∈ㄑ睿現(xiàn)在分析上面的例子母截,我們知道一條緩存行有 64 字節(jié),而 Java 程序的對(duì)象頭固定占 8 字節(jié)(32位系統(tǒng))或 12 字節(jié)( 64 位系統(tǒng)默認(rèn)開(kāi)啟壓縮, 不開(kāi)壓縮為 16 字節(jié))橄教,所以我們只需要填 6 個(gè)無(wú)用的長(zhǎng)整型補(bǔ)上6*8=48字節(jié)清寇,讓不同的 VolatileLong 對(duì)象處于不同的緩存行喘漏,就避免了偽共享( 64 位系統(tǒng)超過(guò)緩存行的 64 字節(jié)也無(wú)所謂,只要保證不同線程不操作同一緩存行就可以)华烟。

偽共享在多核編程中很容易發(fā)生翩迈,而且非常隱蔽。例如盔夜,在 JDK 的 LinkedBlockingQueue 中负饲,存在指向隊(duì)列頭的引用 head 和指向隊(duì)列尾的引用 tail 。而這種隊(duì)列經(jīng)常在異步編程中使有喂链,這兩個(gè)引用的值經(jīng)常的被不同的線程修改返十,但它們卻很可能在同一個(gè)緩存行,于是就產(chǎn)生了偽共享椭微。線程越多吧慢,核越多,對(duì)性能產(chǎn)生的負(fù)面效果就越大赏表。

由于某些 Java 編譯器的優(yōu)化策略,那些沒(méi)有使用到的補(bǔ)齊數(shù)據(jù)可能會(huì)在編譯期間被優(yōu)化掉匈仗,我們可以在程序中加入一些代碼防止被編譯優(yōu)化瓢剿。如下:

public static long preventFromOptimization(VolatileLong v) {  

        return v.p1 + v.p2 + v.p3 + v.p4 + v.p5 + v.p6;  

}

另外一種技術(shù)是使用編譯指示,來(lái)強(qiáng)制使每一個(gè)變量對(duì)齊悠轩。

下面的代碼顯式了編譯器使用__declspec( align(n) ) 此處 n=64间狂,按照 cache line 邊界對(duì)齊。

__declspec (align(64)) int thread1_global_variable;

__declspec (align(64)) int thread2_global_variable;

當(dāng)使用數(shù)組時(shí)火架,在 cache line 尾部填充 padding 來(lái)保證數(shù)據(jù)元素在 cache line 邊界開(kāi)始鉴象。如果不能夠保證數(shù)組按照 cache line 邊界對(duì)齊,填充數(shù)據(jù)結(jié)構(gòu)【數(shù)組元素】使之是 cache line 大小的兩倍何鸡。下面的代碼顯式了填充數(shù)據(jù)結(jié)構(gòu)使之按照 cache line 對(duì)齊纺弊。并且通過(guò) __declspec( align(n) ) 語(yǔ)句來(lái)保證數(shù)組也是對(duì)齊的。如果數(shù)組是動(dòng)態(tài)分配的骡男,你可以增加分配的大小淆游,并調(diào)整指針來(lái)對(duì)其到 cache line 邊界。

struct ThreadParams

{

    // For the following 4 variables: 4*4 = 16 bytes

    unsigned long thread_id;

    unsigned long v; // Frequent read/write access variable

    unsigned long start;

    unsigned long end;

    // expand to 64 bytes to avoid false-sharing 

    // (4 unsigned long variables + 12 padding)*4 = 64

    int padding[12];

};

在jdk1.8中隔盛,有專門的注解@Contended來(lái)避免偽共享犹菱,更優(yōu)雅地解決問(wèn)題。
除此之外吮炕,在網(wǎng)上還有很多對(duì)偽共享的研究腊脱,提出了一些基于數(shù)據(jù)融合的方案,有興趣的同學(xué)可以了解下龙亲。

六陕凹、對(duì)于偽共享悍抑,我們?cè)趯?shí)際開(kāi)發(fā)中該怎么做?

通過(guò)上面大篇幅的介紹捆姜,我們已經(jīng)知道偽共享的對(duì)程序的影響传趾。那么,在實(shí)際的生產(chǎn)開(kāi)發(fā)過(guò)程中泥技,我們一定要通過(guò)緩存行填充去解決掉潛在的偽共享問(wèn)題嗎浆兰?

其實(shí)并不一定。

首先就是多次強(qiáng)調(diào)的珊豹,偽共享是很隱蔽的簸呈,我們暫時(shí)無(wú)法從系統(tǒng)層面上通過(guò)工具來(lái)探測(cè)偽共享事件。其次店茶,不同類型的計(jì)算機(jī)具有不同的微架構(gòu)(如 32 位系統(tǒng)和 64 位系統(tǒng)的 java 對(duì)象所占自己數(shù)就不一樣)蜕便,如果設(shè)計(jì)到跨平臺(tái)的設(shè)計(jì),那就更難以把握了贩幻,一個(gè)確切的填充方案只適用于一個(gè)特定的操作系統(tǒng)轿腺。還有,緩存的資源是有限的丛楚,如果填充會(huì)浪費(fèi)珍貴的 cache 資源族壳,并不適合大范圍應(yīng)用。最后趣些,目前主流的 Intel 微架構(gòu) CPU 的 L1 緩存仿荆,已能夠達(dá)到 80% 以上的命中率。

綜上所述坏平,并不是每個(gè)系統(tǒng)都適合花大量精力去解決潛在的偽共享問(wèn)題拢操。

參考文章:

  • 《從Java視角理解偽共享(False Sharing)》
  • 《【翻譯】線程間偽共享的避免和識(shí)別》
  • 一種利用數(shù)據(jù)融合來(lái)提高局部性和減少偽共享的方法》
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市舶替,隨后出現(xiàn)的幾起案子令境,更是在濱河造成了極大的恐慌,老刑警劉巖坎穿,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件展父,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡玲昧,警方通過(guò)查閱死者的電腦和手機(jī)栖茉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)孵延,“玉大人吕漂,你說(shuō)我怎么就攤上這事〕居Γ” “怎么了惶凝?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵吼虎,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我苍鲜,道長(zhǎng)思灰,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任混滔,我火速辦了婚禮洒疚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘坯屿。我一直安慰自己油湖,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布领跛。 她就那樣靜靜地躺著乏德,像睡著了一般。 火紅的嫁衣襯著肌膚如雪吠昭。 梳的紋絲不亂的頭發(fā)上喊括,一...
    開(kāi)封第一講書(shū)人閱讀 49,760評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音矢棚,去河邊找鬼瘾晃。 笑死,一個(gè)胖子當(dāng)著我的面吹牛幻妓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播劫拢,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼肉津,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了舱沧?” 一聲冷哼從身側(cè)響起妹沙,我...
    開(kāi)封第一講書(shū)人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎熟吏,沒(méi)想到半個(gè)月后距糖,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡牵寺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年悍引,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片帽氓。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡挡育,死狀恐怖肤寝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情韭畸,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布仲智,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏漫仆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一泪幌、第九天 我趴在偏房一處隱蔽的房頂上張望盲厌。 院中可真熱鬧,春花似錦座菠、人聲如沸狸眼。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拓萌。三九已至,卻和暖如春升略,著一層夾襖步出監(jiān)牢的瞬間微王,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工品嚣, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留炕倘,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓翰撑,卻偏偏與公主長(zhǎng)得像罩旋,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子眶诈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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