libdispatch-類型解密

概述

GCD是一套強大的多線程方案拗慨,提供了多種任務(wù)隊列來提高開發(fā)效率,通過閱讀libdispatch的源碼可以更好的理解GCD的工作流程韩脏,幫助我們設(shè)計更好的代碼

結(jié)構(gòu)類型

libdispatch使用宏定義實現(xiàn)了大量的模板結(jié)構(gòu)類型脉让,除此之外還使用了unionenum結(jié)合的方式實現(xiàn)動態(tài)參數(shù)類型的靈活性:

  • queue_type:隊列類型晌杰,例如全局隊列
  • source_type:資源統(tǒng)稱跷睦,queue或者function都可以看做是一個資源
  • semaphore_type:信號類型,信號可以保證資源同時多線程競爭下的安全
  • continuation_type:派發(fā)任務(wù)會被封裝成dispatch_continuation_t肋演,然后被壓入隊列中
enum {  
    _DISPATCH_CONTINUATION_TYPE     =    0x00000, // meta-type for continuations  
    _DISPATCH_QUEUE_TYPE            =    0x10000, // meta-type for queues  
    _DISPATCH_SOURCE_TYPE           =    0x20000, // meta-type for sources  
    _DISPATCH_SEMAPHORE_TYPE        =    0x30000, // meta-type for semaphores  
    _DISPATCH_ATTR_TYPE             = 0x10000000, // meta-type for attribute structures  
  
    DISPATCH_CONTINUATION_TYPE      = _DISPATCH_CONTINUATION_TYPE,  
  
    DISPATCH_QUEUE_ATTR_TYPE        = _DISPATCH_QUEUE_TYPE | _DISPATCH_ATTR_TYPE,  

    DISPATCH_QUEUE_TYPE             = 1 | _DISPATCH_QUEUE_TYPE,  
    DISPATCH_QUEUE_GLOBAL_TYPE      = 2 | _DISPATCH_QUEUE_TYPE,  
    DISPATCH_QUEUE_MGR_TYPE         = 3 | _DISPATCH_QUEUE_TYPE,  

    DISPATCH_SEMAPHORE_TYPE         = _DISPATCH_SEMAPHORE_TYPE,  
  
    DISPATCH_SOURCE_ATTR_TYPE       = _DISPATCH_SOURCE_TYPE | _DISPATCH_ATTR_TYPE,  
  
    DISPATCH_SOURCE_KEVENT_TYPE     = 1 | _DISPATCH_SOURCE_TYPE,  
};

對于libdispatch的結(jié)構(gòu)體類型來說抑诸,都存在DISPATCH_STRUCT_HEADER(x)類型的變量。通過##拼接變量名的方式對不同的類型生成動態(tài)的參數(shù)變量

#define DISPATCH_STRUCT_HEADER(x) \
    _OS_OBJECT_HEADER( \
    const struct dispatch_##x##_vtable_s *do_vtable, \
    do_ref_cnt, \
    do_xref_cnt); \
    struct dispatch_##x##_s *volatile do_next; \
    struct dispatch_queue_s *do_targetq; \
    void *do_ctxt; \
    void *do_finalizer; \
    unsigned int do_suspend_cnt;

聯(lián)合體union保證了各變量享用同一個內(nèi)存地址爹殊,這也意味著各變量是互斥的哼鬓。通過聯(lián)合體結(jié)構(gòu),libdispatch實現(xiàn)了類型強制轉(zhuǎn)換的效果边灭。另外异希,通過宏定義DISPATCH_DECL(name)來保證所有dispatch_xxx_t變量實際上是一個指向dispatch_xxx_s的指針:

typedef union {
    struct _os_object_s *_os_obj;
    struct dispatch_object_s *_do;
    struct dispatch_continuation_s *_dc;
    struct dispatch_queue_s *_dq;
    struct dispatch_queue_attr_s *_dqa;
    struct dispatch_group_s *_dg;
    struct dispatch_source_s *_ds;
    struct dispatch_source_attr_s *_dsa;
    struct dispatch_semaphore_s *_dsema;
    struct dispatch_data_s *_ddata;
    struct dispatch_io_s *_dchannel;
    struct dispatch_operation_s *_doperation;
    struct dispatch_disk_s *_ddisk;
} dispatch_object_t __attribute__((__transparent_union__));

#define DISPATCH_DECL(name) typedef struct name##_s *name##_t

一方面,union在使用時會分配一塊足夠大的內(nèi)存(能夠容納任一一種類型)绒瘦,這意味著可以隨時更換存儲數(shù)據(jù)称簿,同樣可能造成數(shù)據(jù)的破壞;另一方面惰帽,它讓C函數(shù)擁有了返回參數(shù)多樣化的靈活性憨降。但是要記住不同的數(shù)據(jù)類型在生成自身的do_vtable也有不同的表現(xiàn):

/// GCD數(shù)據(jù)類型vtable屬性初始化
DISPATCH_VTABLE_INSTANCE(semaphore,
    .do_type = DISPATCH_SEMAPHORE_TYPE,
    .do_kind = "semaphore",
    .do_dispose = _dispatch_semaphore_dispose,
    .do_debug = _dispatch_semaphore_debug,
);

DISPATCH_VTABLE_INSTANCE(group,
    .do_type = DISPATCH_GROUP_TYPE,
    .do_kind = "group",
    .do_dispose = _dispatch_semaphore_dispose,
    .do_debug = _dispatch_semaphore_debug,
);

DISPATCH_VTABLE_INSTANCE(queue,
    .do_type = DISPATCH_QUEUE_TYPE,
    .do_kind = "queue",
    .do_dispose = _dispatch_queue_dispose,
    .do_invoke = NULL,
    .do_probe = (void *)dummy_function_r0,
    .do_debug = dispatch_queue_debug,
);

DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_root, queue,
    .do_type = DISPATCH_QUEUE_GLOBAL_TYPE,
    .do_kind = "global-queue",
    .do_debug = dispatch_queue_debug,
    .do_probe = _dispatch_queue_probe_root,
);

DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_mgr, queue,
    .do_type = DISPATCH_QUEUE_MGR_TYPE,
    .do_kind = "mgr-queue",
    .do_invoke = _dispatch_mgr_thread,
    .do_debug = dispatch_queue_debug,
    .do_probe = _dispatch_mgr_wakeup,
);

DISPATCH_VTABLE_INSTANCE(queue_specific_queue,
    .do_type = DISPATCH_QUEUE_SPECIFIC_TYPE,
    .do_kind = "queue-context",
    .do_dispose = _dispatch_queue_specific_queue_dispose,
    .do_invoke = NULL,
    .do_probe = (void *)dummy_function_r0,
    .do_debug = (void *)dispatch_queue_debug,
);

DISPATCH_VTABLE_INSTANCE(queue_attr,
    .do_type = DISPATCH_QUEUE_ATTR_TYPE,
    .do_kind = "queue-attr",
);

DISPATCH_VTABLE_INSTANCE(source,
    .do_type = DISPATCH_SOURCE_KEVENT_TYPE,
    .do_kind = "kevent-source",
    .do_invoke = _dispatch_source_invoke,
    .do_dispose = _dispatch_source_dispose,
    .do_probe = _dispatch_source_probe,
    .do_debug = _dispatch_source_debug,
);

queue為例,其結(jié)構(gòu)類型為dispatch_queue_s该酗,鑒于源碼中使用了大量的宏定義增加屬性授药,下面的結(jié)構(gòu)是替換一部分宏定義后的結(jié)構(gòu):

struct dispatch_queue_s {
    DISPATCH_STRUCT_HEADER(queue);

    uint32_t volatile dq_running; 
    uint32_t dq_width;
    struct dispatch_object_s *volatile dq_items_tail;
    struct dispatch_object_s *volatile dq_items_head; 
    unsigned long dq_serialnum; 
    dispatch_queue_t dq_specific_q;

    char dq_label[DISPATCH_QUEUE_MIN_LABEL_SIZE];
    char _dq_pad[DISPATCH_QUEUE_CACHELINE_PAD];
};

一個線程任務(wù)隊列有這么幾個重要的屬性:

  • do_vtable
    虛擬表,采用類似于C++的模板方式定義了一個結(jié)構(gòu)體vtable呜魄,主要存儲了結(jié)構(gòu)類型相關(guān)的描述信息

  • do_targetq
    目標(biāo)隊列悔叽,GCD允許我們將某個任務(wù)隊列指派到另外的任務(wù)隊列中執(zhí)行。當(dāng)do_targetq不為空時爵嗅,async的實現(xiàn)有會所不同

  • dq_width
    最大并發(fā)數(shù)娇澎,串行/主線程的這個值是1

  • do_ctxt
    線程上下文,用來存儲線程池相關(guān)數(shù)據(jù)睹晒,比如用于線程掛起和喚醒的信號量趟庄、線程池尺寸等

    struct dispatch_root_queue_context_s {
        union {
            struct {
                unsigned int volatile dgq_pending;
                dispatch_semaphore_t dgq_thread_mediator;
                uint32_t dgq_thread_pool_size;
            };
            char _dgq_pad[DISPATCH_CACHELINE_SIZE];
        };
    };
    
  • do_suspend_cnt
    用作暫停標(biāo)記,當(dāng)大于等于2時表示任務(wù)為延時任務(wù)伪很。在任務(wù)達(dá)到時會修改標(biāo)記戚啥,然后喚醒隊列調(diào)度任務(wù)


派發(fā)任務(wù)會被包裝成dispatch_continuation_s結(jié)構(gòu)體對象,同樣

#define DISPATCH_OBJ_ASYNC_BIT      0x1
#define DISPATCH_OBJ_BARRIER_BIT    0x2
#define DISPATCH_OBJ_GROUP_BIT      0x4
#define DISPATCH_OBJ_SYNC_SLOW_BIT  0x8

#define DISPATCH_CONTINUATION_HEADER(x) \
    _OS_OBJECT_HEADER( \
    const void *do_vtable, \
    do_ref_cnt, \
    do_xref_cnt); \
    struct dispatch_##x##_s *volatile do_next; \
    dispatch_function_t dc_func; \
    void *dc_ctxt; \
    void *dc_data; \
    void *dc_other;

struct dispatch_continuation_s {
    DISPATCH_CONTINUATION_HEADER(continuation);
};

一個dispatch_continuation_s變量不總是只包裝了單個任務(wù)锉试,它被設(shè)置成復(fù)用機制猫十,通過TSD的方式保證每個線程可以擁有一定數(shù)量的復(fù)用continuation,以此來減少不必要的內(nèi)存分配開銷。

  • dc_func
    承擔(dān)執(zhí)行任務(wù)的對象炫彩,宏定義_dispatch_client_callout最終會以dc_func(dc_ctxt)的方式回調(diào)

  • dc_ctxt
    存儲了continuation對象的上下文數(shù)據(jù),同樣用于執(zhí)行任務(wù)

  • do_vtable
    只有當(dāng)do_vtable的值小于127時才表示變量是一個continuation絮短,派發(fā)到主/串行隊列的任務(wù)會被標(biāo)記DISPATCH_OBJ_BARRIER_BIT屏障標(biāo)記


libdispatch的結(jié)構(gòu)對象都擁有自己的一張do_vtable虛擬表江兢,同樣采用模板式的方式生成,每一個具體的結(jié)構(gòu)類型會生成一張對應(yīng)類型的虛擬表丁频,但是屬性基本是統(tǒng)一的

#define DISPATCH_VTABLE_HEADER(x) \
    unsigned long const do_type; \
    const char *const do_kind; \
    size_t (*const do_debug)(struct dispatch_##x##_s *, char *, size_t); \
    struct dispatch_queue_s *(*const do_invoke)(struct dispatch_##x##_s *); \
    bool (*const do_probe)(struct dispatch_##x##_s *); \
    void (*const do_dispose)(struct dispatch_##x##_s *)

虛擬表采用類型名拼接的方式生成不同類型的重載函數(shù)杉允,由于libdispatch類型采用union結(jié)構(gòu),這兩者結(jié)合極大的保證了執(zhí)行的靈活性

  • do_type
    數(shù)據(jù)的具體類型席里,詳見上文中的枚舉值

  • do_kind
    數(shù)據(jù)的類型描述字符串叔磷,比如全局隊列為global-queue

  • do_debug
    debug方法,用來獲取調(diào)試時需要的變量信息字符串

  • do_probe
    用于檢測傳入對象中的一些值是否滿足條件

  • do_invoke
    喚醒隊列的方法奖磁,全局隊列和主隊列此項為NULL


disaptch_root_queue_context_s存儲了線程運行過程中的上下文數(shù)據(jù)改基,默認(rèn)情況下已經(jīng)創(chuàng)建了多個全局的上下文對象

struct dispatch_root_queue_context_s {
    union {
        struct {
            unsigned int volatile dgq_pending;
#if DISPATCH_USE_PTHREAD_POOL
            dispatch_semaphore_t dgq_thread_mediator;
            uint32_t dgq_thread_pool_size;
#endif
        };
        char _dgq_pad[DISPATCH_CACHELINE_SIZE];
    };
};

上下文對象在執(zhí)行任務(wù)的過程中存儲了線程信號信息以及可用線程池數(shù)量。

  • dgq_pending咖为、_dgq_pad
    當(dāng)前版本源碼中未使用

  • dgq_thread_mediator
    用于判斷是否存在可用線程資源秕狰,如果存在返回1,否則后續(xù)將創(chuàng)建新線程執(zhí)行任務(wù)

  • dgq_thread_pool_size
    線程池剩余可用數(shù)量躁染,只有dgq_thread_mediator的查詢返回0并且此項大于0時會嘗試創(chuàng)建新線程用以執(zhí)行任務(wù)

GCD創(chuàng)建了總共八個四種優(yōu)先級的全局上下文對象,每個上下文最多可容納255個線程數(shù)量

#define MAX_THREAD_COUNT 255
DISPATCH_CACHELINE_ALIGN
static struct dispatch_root_queue_context_s _dispatch_root_queue_contexts[] = {
    [DISPATCH_ROOT_QUEUE_IDX_LOW_PRIORITY] = {{{
#if DISPATCH_USE_PTHREAD_POOL
        .dgq_thread_mediator = &_dispatch_thread_mediator[
                DISPATCH_ROOT_QUEUE_IDX_LOW_PRIORITY],
        .dgq_thread_pool_size = MAX_THREAD_COUNT,
#endif
    }}},
    [DISPATCH_ROOT_QUEUE_IDX_LOW_OVERCOMMIT_PRIORITY] = {{{
#if DISPATCH_USE_PTHREAD_POOL
        .dgq_thread_mediator = &_dispatch_thread_mediator[
                DISPATCH_ROOT_QUEUE_IDX_LOW_OVERCOMMIT_PRIORITY],
        .dgq_thread_pool_size = MAX_THREAD_COUNT,
#endif
    }}},
    [DISPATCH_ROOT_QUEUE_IDX_DEFAULT_PRIORITY] = {{{
#if DISPATCH_USE_PTHREAD_POOL
        .dgq_thread_mediator = &_dispatch_thread_mediator[
                DISPATCH_ROOT_QUEUE_IDX_DEFAULT_PRIORITY],
        .dgq_thread_pool_size = MAX_THREAD_COUNT,
#endif
    }}},
    [DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY] = {{{
#if DISPATCH_USE_PTHREAD_POOL
        .dgq_thread_mediator = &_dispatch_thread_mediator[
                DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY],
        .dgq_thread_pool_size = MAX_THREAD_COUNT,
#endif
    }}},
    [DISPATCH_ROOT_QUEUE_IDX_HIGH_PRIORITY] = {{{
#if DISPATCH_USE_PTHREAD_POOL
        .dgq_thread_mediator = &_dispatch_thread_mediator[
                DISPATCH_ROOT_QUEUE_IDX_HIGH_PRIORITY],
        .dgq_thread_pool_size = MAX_THREAD_COUNT,
#endif
    }}},
    [DISPATCH_ROOT_QUEUE_IDX_HIGH_OVERCOMMIT_PRIORITY] = {{{
#if DISPATCH_USE_PTHREAD_POOL
        .dgq_thread_mediator = &_dispatch_thread_mediator[
                DISPATCH_ROOT_QUEUE_IDX_HIGH_OVERCOMMIT_PRIORITY],
        .dgq_thread_pool_size = MAX_THREAD_COUNT,
#endif
    }}},
    [DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_PRIORITY] = {{{
#if DISPATCH_USE_PTHREAD_POOL
        .dgq_thread_mediator = &_dispatch_thread_mediator[
                DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_PRIORITY],
        .dgq_thread_pool_size = MAX_THREAD_COUNT,
#endif
    }}},
    [DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_OVERCOMMIT_PRIORITY] = {{{
#if DISPATCH_USE_PTHREAD_POOL
        .dgq_thread_mediator = &_dispatch_thread_mediator[
                DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_OVERCOMMIT_PRIORITY],
        .dgq_thread_pool_size = MAX_THREAD_COUNT,
#endif
    }}},
};

dispatch_semaphore_s是性能稍次于自旋鎖的的信號量對象,用來保證資源使用的安全性臭杰。

struct dispatch_semaphore_s {
    DISPATCH_STRUCT_HEADER(semaphore);
    long dsema_value;
    long dsema_orig;
    size_t dsema_sent_ksignals;

    size_t dsema_group_waiters;
    struct dispatch_sema_notify_s *dsema_notify_head;
    struct dispatch_sema_notify_s *dsema_notify_tail;
};

相比其他的結(jié)構(gòu)曹动,信號量的內(nèi)部要簡潔的多,主要使用的就三個屬性:

  • dsema_value
    當(dāng)前信號值饰恕,當(dāng)這個值小于0時無法訪問加鎖資源

  • dsema_orig
    初始化信號值挠羔,限制了同時訪問資源的線程數(shù)量

  • dsema_sent_ksignals
    由于mach信號可能會被意外喚醒,通過原子操作來避免虛假信號


通過一張圖表示上面提及的四種數(shù)據(jù)類型的關(guān)系:


線程私有變量

進(jìn)程中的全局變量和靜態(tài)變量是所有線程都能訪問的共享變量埋嵌,這意味著訪問這樣的數(shù)據(jù)需要昂貴的同步花銷褥赊,Thread-specific-Data線程私有數(shù)據(jù)機制讓每一個線程擁有私有的全局變量。libdispatch提供了四個pthread_key來存取這些數(shù)據(jù)莉恼,在初始化階段進(jìn)行初始化:

DISPATCH_EXPORT DISPATCH_NOTHROW
void
libdispatch_init(void)
{
    dispatch_assert(DISPATCH_QUEUE_PRIORITY_COUNT == 4);
    dispatch_assert(DISPATCH_ROOT_QUEUE_COUNT == 8);

    dispatch_assert(DISPATCH_QUEUE_PRIORITY_LOW ==
            -DISPATCH_QUEUE_PRIORITY_HIGH);
    dispatch_assert(countof(_dispatch_root_queues) ==
            DISPATCH_ROOT_QUEUE_COUNT);
    dispatch_assert(countof(_dispatch_root_queue_contexts) ==
            DISPATCH_ROOT_QUEUE_COUNT);

    ......

    _dispatch_thread_key_create(&dispatch_queue_key, _dispatch_queue_cleanup);
    _dispatch_thread_key_create(&dispatch_sema4_key,
            (void (*)(void *))_dispatch_thread_semaphore_dispose);
    _dispatch_thread_key_create(&dispatch_cache_key, _dispatch_cache_cleanup);
    _dispatch_thread_key_create(&dispatch_io_key, NULL);
    _dispatch_thread_key_create(&dispatch_apply_key, NULL);
#if DISPATCH_PERF_MON
    _dispatch_thread_key_create(&dispatch_bcounter_key, NULL);
#endif
    ......
}

基于大概率使用的派發(fā)任務(wù)拌喉,libdispatch緩存了dispatch_continuation_s,采用復(fù)用模式的做法在每次async中嘗試去獲取空閑的continuation變量俐银,通過兩個函數(shù)存取數(shù)據(jù):

void* _Nullable pthread_getspecific(pthread_key_t);
int pthread_setspecific(pthread_key_t , const void * _Nullable);
最后編輯于
?著作權(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
  • 文/潘曉璐 我一進(jìn)店門景醇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來臀稚,“玉大人,你說我怎么就攤上這事三痰“伤拢” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵散劫,是天一觀的道長稚机。 經(jīng)常有香客問我,道長获搏,這世上最難降的妖魔是什么赖条? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮常熙,結(jié)果婚禮上谋币,老公的妹妹穿的比我還像新娘。我一直安慰自己症概,他們只是感情好蕾额,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著彼城,像睡著了一般诅蝶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上募壕,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天调炬,我揣著相機與錄音,去河邊找鬼舱馅。 笑死缰泡,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的代嗤。 我是一名探鬼主播棘钞,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼干毅!你這毒婦竟也來了宜猜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤硝逢,失蹤者是張志新(化名)和其女友劉穎姨拥,沒想到半個月后绅喉,有當(dāng)?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
  • 正文 我出身青樓象浑,卻偏偏與公主長得像蔫饰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子愉豺,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

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