OC多線(xiàn)程學(xué)習(xí)(二) - GCD

本文內(nèi)容:

  1. GCD相關(guān)概念
  2. 有關(guān)GCD的幾道面試題
  3. 源碼分析:隊(duì)列和異步函數(shù)

GCD概念


GCD是Grand Central Dispatch的縮寫(xiě)赔硫。是蘋(píng)果為提供多核并行運(yùn)算而提出的解決方案贡这。主要功能作用:將任務(wù)添加到隊(duì)列,并且指定執(zhí)行任務(wù)的函數(shù)眼滤。而且開(kāi)發(fā)人員不需要編寫(xiě)管理線(xiàn)程生命周期的代碼赏僧。

任務(wù)

GCD中的任務(wù)用block封裝大猛,并有以下特點(diǎn):

  • 任務(wù)block沒(méi)有參數(shù)也沒(méi)有返回值
  • 不需要手動(dòng)調(diào)用block,GCD內(nèi)部幫我們調(diào)用

函數(shù)

GCD中的函數(shù)總體分為:同步函數(shù)dispatch_sync和異步函數(shù)dispatch_async

  • 同步函數(shù)dispatch_sync
    • 等待當(dāng)前語(yǔ)句執(zhí)行完畢
    • 不會(huì)開(kāi)啟線(xiàn)程
    • 在當(dāng)前線(xiàn)程執(zhí)行任務(wù)
  • 異步函數(shù)dispatch_async
    • 不用等待當(dāng)前語(yǔ)句執(zhí)行完畢
    • 會(huì)開(kāi)啟線(xiàn)程新線(xiàn)程執(zhí)行任務(wù)

隊(duì)列

隊(duì)列是一種數(shù)據(jù)結(jié)構(gòu)淀零。具有先進(jìn)先出的特性挽绩。GCD中大致分為兩種隊(duì)列類(lèi)型,串行隊(duì)列并發(fā)隊(duì)列驾中。

根據(jù)調(diào)用不同的函數(shù)(同步or異步)唉堪,會(huì)有以下四種情況:

--- 同步函數(shù) 異步函數(shù)
串行隊(duì)列 1.不會(huì)開(kāi)啟線(xiàn)程 2.任務(wù)按順序執(zhí)行 3.會(huì)產(chǎn)生堵塞 1.開(kāi)啟新線(xiàn)程 2.任務(wù)按順序執(zhí)行
并發(fā)隊(duì)列 1.不會(huì)開(kāi)啟線(xiàn)程 2.任務(wù)按順序執(zhí)行 1.開(kāi)啟新線(xiàn)程 2.任務(wù)異步執(zhí)行,沒(méi)有順序链方,與CPU的調(diào)度有關(guān)

隊(duì)列和線(xiàn)程的關(guān)系

面試的時(shí)候經(jīng)常會(huì)被問(wèn)到隊(duì)列和線(xiàn)程之間的關(guān)系祟蚀?
其實(shí)他們是沒(méi)有太大的關(guān)系的前酿,隊(duì)列是一種數(shù)據(jù)結(jié)構(gòu)薪者,作為任務(wù)的容器剿涮。線(xiàn)程是進(jìn)程的基本執(zhí)行單元取试,是任務(wù)的執(zhí)行者瞬浓。CPU調(diào)度線(xiàn)程去執(zhí)行容器中的任務(wù)猿棉。所以說(shuō)隊(duì)列和線(xiàn)程沒(méi)有直接關(guān)系萨赁,只是在不同業(yè)務(wù)層級(jí)中擔(dān)當(dāng)不同的角色罷了杖爽。

GCD一些相關(guān)面試題


面試題1:
dispatch_queue_t queue = dispatch_queue_create("HelloGCD", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
    NSLog(@"2");
    dispatch_async(queue, ^{
        NSLog(@"3");
    });
    NSLog(@"4");
});
NSLog(@"5");

答案:1慰安、5化焕、2撒桨、4、3
分析:隊(duì)列是并行隊(duì)列赖阻,兩次調(diào)用異步函數(shù)(dispatch_async)火欧,都會(huì)開(kāi)啟新的線(xiàn)程執(zhí)行任務(wù)茎截,并且不會(huì)堵塞當(dāng)前線(xiàn)程企锌。

  1. 首先打印“1”,遇到異步函數(shù)不處理撕攒,然后打印“5”抖坪。
  2. 第一層異步函數(shù)內(nèi)執(zhí)行邏輯與外部類(lèi)似擦俐,打印“2”和“4”蚯瞧。
  3. 最后執(zhí)行第二次異步函數(shù),打印“3”
面試題2
dispatch_queue_t queue = dispatch_queue_create("HelloGCD", DISPATCH_QUEUE_CONCURRENT);
    
dispatch_async(queue, ^{
    // sleep(2);
    NSLog(@"1");
});
dispatch_async(queue, ^{
    NSLog(@"2");
});
// 堵塞
dispatch_sync(queue, ^{
    NSLog(@"3");
});
// **********************
NSLog(@"0");

dispatch_async(queue, ^{
    NSLog(@"7");
});
dispatch_async(queue, ^{
    NSLog(@"8");
});
dispatch_async(queue, ^{
    NSLog(@"9");
});
// A: 1230789
// B: 1237890
// C: 3120798
// D: 2137890

答案:AC
分析:并行隊(duì)列添加相關(guān)任務(wù)备徐,其中“3”是同步函數(shù)坦喘,會(huì)堵塞(堵塞的代碼行在同步函數(shù)代碼行結(jié)束的位置瓣铣,也就是當(dāng)前代碼NSLog(@"3");下一行的“});”)當(dāng)前線(xiàn)程棠笑”途龋“0”在主線(xiàn)程中執(zhí)行,其他的都是異步函數(shù)斩例,所以“0”后面的異步函數(shù)肯定都會(huì)在“0”之后執(zhí)行念赶。因此本題答案是“3”在“0”之前叉谜,并且“7”踩萎、“8”香府、“9”在“0”之后裆泳。因此答案是AC柠硕。注意:“1”和“2”的位置不確定蝗柔,這個(gè)取決于任務(wù)的時(shí)間復(fù)雜度癣丧,可以打開(kāi)“1”中的sleep胁编,打印查看一下結(jié)果嬉橙,“1”會(huì)在“9”之后打印寥假。

面試題3:
// 串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("HelloGCD", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
// 異步函數(shù)
dispatch_async(queue, ^{
    NSLog(@"2");
    dispatch_sync(queue, ^{
        NSLog(@"3");
    });
    NSLog(@"4");
});
NSLog(@"5");

答案:1糕韧、5喻圃、2斧拍、崩潰(EXC_BAD_INSTRUCTION)
分析:

  1. 在主線(xiàn)程隊(duì)列中(串行隊(duì)列)肆汹,依次加入“1”县踢、異步函數(shù)(dispatch_async)代碼塊硼啤、“5”
  2. 異步函數(shù)開(kāi)啟子線(xiàn)程谴返,不阻塞主線(xiàn)程嗓袱,所以先打印“1”和“5”渠抹。
  3. 子線(xiàn)程中梧却,由于是串行隊(duì)列放航,所以會(huì)把“2”广鳍、同步函數(shù)dispatch_sync赊时、“4”這三個(gè)“任務(wù)”依次加入到queue中蛋叼。
  4. 子線(xiàn)程開(kāi)始串行執(zhí)行任務(wù)狐胎,打印“2”
  5. 隊(duì)列下一個(gè)任務(wù)是同步函數(shù)握巢,會(huì)阻塞當(dāng)前隊(duì)列松却,然后把“3”加入到隊(duì)列中晓锻,此時(shí)會(huì)產(chǎn)生死鎖独撇,此時(shí)隊(duì)列情況:==dispatch_sync的block - “4” - “3”==
    • 同步函數(shù)需要“3”執(zhí)行完躁锁,自己才能執(zhí)行結(jié)束战转。
    • 由于“3”是在“4”后面加入到隊(duì)列啄踊,所以“3”要等待“4”執(zhí)行完成刁标。
    • “4”在同步函數(shù)后面加入到隊(duì)列,所以得等待同步函數(shù)執(zhí)行結(jié)束吏砂。
    • 等待情況:dispatch_sync - “3” - “4” - dispatch_sync狐血,是互相等待的狀態(tài)匈织,因此出現(xiàn)了死鎖。
面試題4
__block int a = 0;
while (a < 5) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        a++;
    });
}
    
NSLog(@"out a = %d", a);

問(wèn)題1:最后的打印
A:0 B:<5 C:=5 D:>5
答案:CD
分析:
a初始化=0缀匕,進(jìn)入while循環(huán)纳决,循環(huán)條件是a<5,所以當(dāng)a小于5的時(shí)候乡小,都會(huì)在while循環(huán)中阔加,所以可以排除A和B。循環(huán)中使用的是異步函數(shù)满钟,異步函數(shù)會(huì)開(kāi)辟線(xiàn)程胜榔,所以當(dāng)其中一個(gè)線(xiàn)程的操作a++后滿(mǎn)足跳出循環(huán)的條件了,就會(huì)退出循環(huán)湃番,但是此時(shí)可能還會(huì)有其他線(xiàn)程還沒(méi)有執(zhí)行完夭织,就會(huì)有a>=5的情況尊惰。因此答案是CD逾条。

問(wèn)題2:如何獲取到循環(huán)中最后的a值
答案:在while循環(huán)外,使用相同的隊(duì)列中,再次調(diào)用異步函數(shù)安券。

...

NSLog(@"out a = %d", a);
// add code 
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    sleep(1);
    NSLog(@"out a = %d", a);
});
// end add

分析:
在相同隊(duì)列中址貌,以相同的方式(異步函數(shù))再追加一個(gè)任務(wù),任務(wù)內(nèi)容是打印a赠制,為了隊(duì)列中其他任務(wù)執(zhí)行完畢政恍,此處增加一個(gè)sleep宗弯,因?yàn)槿蝿?wù)都比較簡(jiǎn)單(NSLog)邓厕,就算不加也不會(huì)有太大的問(wèn)題昧互。
這個(gè)問(wèn)題在正常開(kāi)發(fā)中不會(huì)使用到贿讹,而且會(huì)浪費(fèi)很大的性能(會(huì)有很多無(wú)用的線(xiàn)程執(zhí)行無(wú)用的任務(wù))面殖。目的只是考餐對(duì)GCD隊(duì)列的了解程度辽幌。

問(wèn)題3:如何進(jìn)行性能優(yōu)化
答案:
用信號(hào)量加鎖的方式

dispatch_semaphore_t s = dispatch_semaphore_create(1);
__block int a = 0;
while (a < 5) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"in a = %d, %@", a, [NSThread currentThread]);
        a++;
        dispatch_semaphore_signal(s);
    });
    dispatch_semaphore_wait(s, DISPATCH_TIME_FOREVER);
}
    
NSLog(@"out a = %d", a);

分析:
此處信號(hào)量wait方法哭当,如果放在異步函數(shù)(dispatch_async)調(diào)用之前脖岛,那么異步函數(shù)的就沒(méi)有使用的意義了(編程順序執(zhí)行绍在,可以把異步函數(shù)的代碼刪掉了)溜宽。放在異步函數(shù)之后嫉嘀,異步函數(shù)還是有意義的,不懂的可以自己打印看看打印結(jié)果推掸。信號(hào)量加鎖的方式很容易理解,但是兩個(gè)函數(shù)放的位置廊遍,還有根據(jù)具體的業(yè)務(wù)需求來(lái)自行決定见咒。

底層分析

源碼libdispatch下載地址

隊(duì)列創(chuàng)建源碼分析

隊(duì)列也是對(duì)象,通過(guò)一個(gè)示例證明一下:


  • 代碼中創(chuàng)建兩個(gè)隊(duì)列對(duì)象,一個(gè)是串行隊(duì)列腾仅,另一個(gè)是并發(fā)隊(duì)列
  • 通過(guò)runtime的api方法object_getClass推励,查看他們的歸屬類(lèi):
    • 串行隊(duì)列:OS_dispatch_queue_serial
    • 并發(fā)隊(duì)列:OS_dispatch_queue_concurrent
  • 接下來(lái)通過(guò)查看源碼找到類(lèi)名創(chuàng)建隊(duì)列的地方,以及isa的指向
dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
{
    return _dispatch_lane_create_with_target(label, attr,
            DISPATCH_TARGET_QUEUE_DEFAULT, true);
}

static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
        dispatch_queue_t tq, bool legacy)
{

    dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
    
    ......
    
    const void *vtable;
    if (dqai.dqai_concurrent) {
        vtable = DISPATCH_VTABLE(queue_concurrent);
    } else {
        vtable = DISPATCH_VTABLE(queue_serial);
    }
    
    ......
    
    dispatch_lane_t dq = _dispatch_object_alloc(vtable,
            sizeof(struct dispatch_lane_s)); // alloc
    _dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
            DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
            (dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0)); // init
            
    ......
}
  • dispatch_queue_create函數(shù)有兩個(gè)參數(shù):
    • 第一個(gè)label:字符標(biāo)示
    • 第二個(gè)attr:表示串行隊(duì)列還是并發(fā)隊(duì)列
  • 緊接著調(diào)用_dispatch_lane_create_with_target函數(shù)醉拓,前兩個(gè)參數(shù)就是dispatch_queue_create的兩個(gè)參數(shù),第二個(gè)參數(shù)dqa就是attr
  • 對(duì)dqa封裝成dispatch_queue_attr_info_t類(lèi)型,變量是dqai伦乔,這里對(duì)傳入的參數(shù)進(jìn)行了判斷厉亏,賦值給dqai.dqai_concurrent,然后DISPATCH_VTABLE宏獲取不同隊(duì)列的類(lèi)名存入到vtable變量中
  • 后續(xù)調(diào)用_dispatch_object_alloc進(jìn)行內(nèi)存分配
void *
_dispatch_object_alloc(const void *vtable, size_t size)
{
#if OS_OBJECT_HAVE_OBJC1
//不關(guān)心的代碼
    .....
#else
    return _os_object_alloc_realized(vtable, size);
#endif
}

inline _os_object_t
_os_object_alloc_realized(const void *cls, size_t size)
{
    _os_object_t obj;
    dispatch_assert(size >= sizeof(struct _os_object_s));
    while (unlikely(!(obj = calloc(1u, size)))) {
        _dispatch_temporary_resource_shortage();
    }
    obj->os_obj_isa = cls;//isa賦值
    return obj;
}
  • 調(diào)用_dispatch_object_alloc函數(shù)烈和,間接調(diào)用_os_object_alloc_realized函數(shù)
  • _os_object_alloc_realized中看到了isa賦值代碼:obj->os_obj_isa = cls;
  • 到此就我們就了解隊(duì)列對(duì)象的整個(gè)初始化過(guò)程爱只。

異步函數(shù)源碼分析

主要的研究目標(biāo)是任務(wù)block是如何被調(diào)用的。

dispatch_async(queue_c, ^{
    NSLog(@"12334");
});
  • dispatch_async有兩個(gè)參數(shù)招刹,第一個(gè)參數(shù)是隊(duì)列恬试,第二個(gè)是任務(wù)(block)
void
dispatch_async(dispatch_queue_t dq, dispatch_block_t work)
{
    dispatch_continuation_t dc = _dispatch_continuation_alloc();
    uintptr_t dc_flags = DC_FLAG_CONSUME;
    dispatch_qos_t qos;

    qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
    _dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}
  • work參數(shù)被傳入到_dispatch_continuation_init函數(shù)中的第三個(gè)參數(shù),其余的地方?jīng)]有用到
static inline dispatch_qos_t
_dispatch_continuation_init(dispatch_continuation_t dc,
        dispatch_queue_class_t dqu, dispatch_block_t work,
        dispatch_block_flags_t flags, uintptr_t dc_flags)
{
    //封裝work成ctxt
    void *ctxt = _dispatch_Block_copy(work);

    dc_flags |= DC_FLAG_BLOCK | DC_FLAG_ALLOCATED;
    if (unlikely(_dispatch_block_has_private_data(work))) {
        dc->dc_flags = dc_flags;
        dc->dc_ctxt = ctxt;
        // will initialize all fields but requires dc_flags & dc_ctxt to be set
        return _dispatch_continuation_init_slow(dc, dqu, flags);
    }

    //封裝work成func
    dispatch_function_t func = _dispatch_Block_invoke(work);
    if (dc_flags & DC_FLAG_CONSUME) {
        func = _dispatch_call_block_and_release;
    }
    //ctxt和func作為參數(shù)傳入
    return _dispatch_continuation_init_f(dc, dqu, ctxt, func, flags, dc_flags);
}
  • work被封裝成ctxtfunc蔗喂,然后傳入到_dispatch_continuation_init_f函數(shù)中
static inline dispatch_qos_t
_dispatch_continuation_init_f(dispatch_continuation_t dc,
        dispatch_queue_class_t dqu, void *ctxt, dispatch_function_t f,
        dispatch_block_flags_t flags, uintptr_t dc_flags)
{
    pthread_priority_t pp = 0;
    dc->dc_flags = dc_flags | DC_FLAG_ALLOCATED;
    dc->dc_func = f;//保存f
    dc->dc_ctxt = ctxt;//保存ctxt
    // in this context DISPATCH_BLOCK_HAS_PRIORITY means that the priority
    // should not be propagated, only taken from the handler if it has one
    if (!(flags & DISPATCH_BLOCK_HAS_PRIORITY)) {
        pp = _dispatch_priority_propagate();
    }
    _dispatch_continuation_voucher_set(dc, flags);
    return _dispatch_continuation_priority_set(dc, dqu, pp, flags);
}
  • 到此我們看到了任務(wù)block的保存(dc->dc_ctxt = ctxt)和調(diào)用函數(shù)的保存(dc->dc_func = f)忘渔,那么在什么時(shí)候調(diào)用呢?我們就需要查看調(diào)用堆棧了
  • 在任務(wù)block內(nèi)下斷點(diǎn)缰儿,然后bt命令查看調(diào)用棧畦粮。
void
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
    @try {
        return f(ctxt);//之前保存的相關(guān)調(diào)用方法和任務(wù)
    }
    @catch (...) {
        objc_terminate();
    }
}

void
_dispatch_call_block_and_release(void *block)
{
    void (^b)(void) = block;
    b();
    Block_release(b);
}
  • 可以看到最后調(diào)用的是_dispatch_call_block_and_release函數(shù)。這個(gè)函數(shù)就是上面源碼中保存的f乖阵,dc->dc_func = f;
  • 此時(shí)就可以清楚為什么GCD相關(guān)的任務(wù)block不用我們手動(dòng)調(diào)用了宣赔。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瞪浸,隨后出現(xiàn)的幾起案子儒将,更是在濱河造成了極大的恐慌,老刑警劉巖对蒲,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钩蚊,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡砰逻,警方通過(guò)查閱死者的電腦和手機(jī)泛鸟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)闸翅,“玉大人菊霜,你說(shuō)我怎么就攤上這事∫挪ぃ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵叭喜,是天一觀的道長(zhǎng)蓖谢。 經(jīng)常有香客問(wèn)我,道長(zhǎng)啥辨,這世上最難降的妖魔是什么盯腌? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮级乍,結(jié)果婚禮上帚湘,老公的妹妹穿的比我還像新娘。我一直安慰自己大诸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布焙贷。 她就那樣靜靜地躺著建邓,像睡著了一般官边。 火紅的嫁衣襯著肌膚如雪外遇。 梳的紋絲不亂的頭發(fā)上跳仿,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天菲语,我揣著相機(jī)與錄音山上,去河邊找鬼英支。 笑死干花,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的池凄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼肿仑,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼致盟!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起柏副,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎割择,沒(méi)想到半個(gè)月后眷篇,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體荔泳,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蕉饼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年玛歌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了昧港。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡支子,死狀恐怖创肥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情叹侄,我是刑警寧澤巩搏,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布贯底,位于F島的核電站,受9級(jí)特大地震影響胚想,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜芽隆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鸿吆。 院中可真熱鬧囤采,春花似錦、人聲如沸惩淳。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)思犁。三九已至代虾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間激蹲,已是汗流浹背棉磨。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留学辱,地道東北人乘瓤。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像策泣,于是被迫代替她去往敵國(guó)和親衙傀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353