多線程娃弓、鎖和線程同步方案

多線程

多線程技術(shù)大家都很了解,而且在項(xiàng)目中也比較常用岛宦。比如開(kāi)啟一個(gè)子線程來(lái)處理一些耗時(shí)的計(jì)算台丛,然后返回主線程刷新UI等。首先我們先簡(jiǎn)單的梳理一下常用到的多線程方案砾肺。具體的用法這里我就不說(shuō)了挽霉,每一種方案大家可以去查一下,網(wǎng)上教程很多变汪。

常見(jiàn)的多線程方案

我們比較常用的是GCD和NSOperation侠坎,當(dāng)然還有NSThread,pthread裙盾。他們的具體區(qū)別我們不詳細(xì)說(shuō)实胸,給出下面這一個(gè)表格,大家自行對(duì)比一下番官。

對(duì)比.png

容易混淆的術(shù)語(yǔ)

提到多線程庐完,有一個(gè)術(shù)語(yǔ)是經(jīng)常能聽(tīng)到的,同步徘熔,異步门躯,串行,并發(fā)酷师。

同步和異步的區(qū)別讶凉,就是是否有開(kāi)啟新的線程的能力。異步具備開(kāi)啟線程的能力窒升,同步不具備開(kāi)啟線程的能力缀遍。注意,異步只是具備開(kāi)始新線程的能力饱须,具體開(kāi)啟與否還要跟隊(duì)列的屬性有關(guān)系域醇。

串行和并發(fā),是指的任務(wù)的執(zhí)行方式蓉媳。并發(fā)是任務(wù)可以多個(gè)同時(shí)執(zhí)行譬挚,串行之能是一個(gè)執(zhí)行完成后在執(zhí)行下一個(gè)。

在面試的過(guò)程中可能被問(wèn)到什么網(wǎng)情況下會(huì)出現(xiàn)死鎖的問(wèn)題酪呻,總結(jié)一下就是使用sync函數(shù)(同步)往當(dāng)前的串行對(duì)列中添加任務(wù)時(shí)减宣,會(huì)出現(xiàn)死鎖。

多線程的安全隱患

多線程和安全問(wèn)題是分不開(kāi)的玩荠,因?yàn)樵谑褂枚鄠€(gè)線程訪問(wèn)同一塊數(shù)據(jù)的時(shí)候漆腌,如果同時(shí)有讀寫(xiě)操作贼邓,就可能產(chǎn)生數(shù)據(jù)安全問(wèn)題。

所以這時(shí)候我們就用到了鎖這個(gè)東西闷尿。

其實(shí)使用鎖也是為了在使用多線程的過(guò)程中保障數(shù)據(jù)安全塑径,除了鎖,然后一些其他的實(shí)現(xiàn)線程同步來(lái)保證數(shù)據(jù)安全的方案填具,我們一起來(lái)了解一下统舀。

線程同步方案

下面這些是我們常用來(lái)實(shí)現(xiàn)線程同步方案的。

OSSpinLock
os_unfair_lock
pthread_mutex
NSLock
NSRecursiveLock
NSCondition
NSConditinLock
dispatch_semaphore
dispatch_queue(DISPATCH_QUEUE_SERIAL)
@synchronized

可以看出來(lái)劳景,實(shí)現(xiàn)線程同步的方案包括各種鎖誉简,還有信號(hào)量,串行隊(duì)列盟广。

我們只挑其中不常用的來(lái)說(shuō)一下使用方法闷串。
下面是我們模擬了存錢(qián)取錢(qián)的場(chǎng)景,下面是加鎖之前的代碼衡蚂,運(yùn)行之后肯定是有數(shù)據(jù)問(wèn)題的窿克。

/**
 存錢(qián)、取錢(qián)演示
 */
- (void)moneyTest {
    self.money = 100;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self __saveMoney];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self __drawMoney];
        }
    });
}

/**
 存錢(qián)
 */
- (void)__saveMoney {
    int oldMoney = self.money;
    sleep(.2);
    oldMoney += 50;
    self.money = oldMoney;
    
    NSLog(@"存50毛甲,還剩%d元 - %@", oldMoney, [NSThread currentThread]);
    
}

/**
 取錢(qián)
 */
- (void)__drawMoney {
    
    int oldMoney = self.money;
    sleep(.2);
    oldMoney -= 20;
    self.money = oldMoney;
    
    NSLog(@"取20年叮,還剩%d元 - %@", oldMoney, [NSThread currentThread]);
    
}

加鎖的代碼,涉及到鎖的初始化玻募、加鎖只损、解鎖這么三部分。我們從OSSpinLock開(kāi)始說(shuō)七咧。

OSSpinLock自旋鎖

OSSpinLock叫做自旋鎖跃惫。那什么叫自旋鎖呢?其實(shí)我們可以從大類上面把鎖分為兩類艾栋,一類是自旋鎖爆存,一類是互斥鎖。我們通過(guò)一個(gè)例子來(lái)區(qū)分這兩類鎖蝗砾。

如果線程A率先到達(dá)加鎖的部分先较,并成功加鎖,線程B到達(dá)的時(shí)候會(huì)因?yàn)橐呀?jīng)被A加鎖而等待悼粮。如果是自旋鎖闲勺,線程B會(huì)通過(guò)執(zhí)行一個(gè)循環(huán)來(lái)實(shí)現(xiàn)等待,我們不用管它循環(huán)執(zhí)行了什么扣猫,只要知道他在那"轉(zhuǎn)圈圈"等著就行菜循。如果是互斥鎖,那線程B在等待的時(shí)候會(huì)休眠申尤。

使用OSSpinLock需要導(dǎo)入頭文件#import <libkern/OSAtomic.h>

//聲明一個(gè)鎖
@property (nonatomic, assign) OSSpinLock lock;

// 鎖的初始化
self.lock = OS_SPINLOCK_INIT;

在我們這個(gè)例子中癌幕,存錢(qián)取錢(qián)都是訪問(wèn)了money衙耕,所以我們要在存和取的操作中使用同一個(gè)鎖。

/**
 存錢(qián)
 */
- (void)__saveMoney {
    OSSpinLockLock(&_lock);
    
    //....省去中間的邏輯代碼
    
    OSSpinLockUnlock(&_lock);
}

/**
 取錢(qián)
 */
- (void)__drawMoney {
    OSSpinLockLock(&_lock);
    
    //....省去中間的邏輯代碼
    
    OSSpinLockUnlock(&_lock);
}

這就是簡(jiǎn)單的自旋鎖的使用勺远,我們發(fā)現(xiàn)在使用的過(guò)程中臭杰,Xcode一直提醒我們這個(gè)OSSpinLock被廢棄了,讓我們使用os_unfair_lock代替谚中。OSSpinLock之所以會(huì)被廢棄是因?yàn)樗赡軙?huì)產(chǎn)生一個(gè)優(yōu)先級(jí)反轉(zhuǎn)的問(wèn)題。

具體來(lái)說(shuō)寥枝,如果一個(gè)低優(yōu)先級(jí)的線程獲得了鎖并訪問(wèn)共享資源宪塔,那高優(yōu)先級(jí)的線程只能忙等,從而占用大量的CPU囊拜。低優(yōu)先級(jí)的線程無(wú)法和高優(yōu)先級(jí)的線程競(jìng)爭(zhēng)(CPU會(huì)給高優(yōu)先級(jí)的線程分配更多的時(shí)間片)某筐,所以會(huì)導(dǎo)致低優(yōu)先級(jí)的線程的任務(wù)一直完不成,從而無(wú)法釋放鎖冠跷。

os_unfair_lock的用法跟OSSpinLock很像南誊,就不單獨(dú)說(shuō)了。

pthread_mutex
Default

一看到這個(gè)pthread我們應(yīng)該就能知道這是一種跨平臺(tái)的方案了蜜托。首先還是來(lái)看用法抄囚。

//聲明一個(gè)鎖
@property (nonatomic, assign) pthread_mutex_t lock;

//初始化
pthread_mutex_init(pthread_mutex_t *restrict _Nonnull, const pthread_mutexattr_t *restrict _Nullable)

我們可以看到在初始化鎖的時(shí)候,第一個(gè)參數(shù)是鎖的地址橄务,第二個(gè)參數(shù)是一個(gè)pthread_mutexattr_t類型的地址幔托,如果我們不傳pthread_mutexattr_t,直接傳一個(gè)NULL蜂挪,相當(dāng)于創(chuàng)建一個(gè)默認(rèn)的互斥鎖重挑。

//方式一
pthread_mutex_init(mutex, NULL);
//方式二
// - 創(chuàng)建attr
pthread_mutexattr_t attr;
// - 初始化attr
pthread_mutexattr_init(&attr);
// - 設(shè)置attr類型
pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_DEFAULT);
// - 使用attr初始化鎖
pthread_mutex_init(&_lock, &attr);
// - 銷毀attr
pthread_mutexattr_destroy(&attr);

上面兩個(gè)方式是一個(gè)效果,那為什么使用attr棠涮,那就說(shuō)明除了default類型的還有其他類型谬哀,我們后面再說(shuō)。

在使用的時(shí)候用pthread_mutex_lock(&_lock);pthread_mutex_unlock(&_lock);加鎖解鎖严肪。

NSLock就是對(duì)這種普通互斥鎖的OC層面的封裝史煎。

RECURSIVE 遞歸鎖

調(diào)用pthread_mutexattr_settype的時(shí)候如果類型傳入PTHREAD_MUTEX_RECURSIVE,會(huì)創(chuàng)建一個(gè)遞歸鎖诬垂。舉個(gè)例子吧劲室。

// 偽代碼
-(void)test {
    lock;
    [self test];
    unlock;
}

如果是普通的鎖结窘,當(dāng)我們?cè)趖est方法中很洋,遞歸調(diào)用test,應(yīng)該會(huì)出現(xiàn)死鎖隧枫,因?yàn)楸籰ock喉磁,在遞歸調(diào)用時(shí)無(wú)法調(diào)用谓苟,一直等待。但是如果鎖是遞歸鎖协怒,他會(huì)允許同一個(gè)線程多次加鎖和解鎖檐迟,就可以解決這個(gè)問(wèn)題了。

NSRecursiveLock是對(duì)遞歸鎖的封裝甜熔。

Condition 條件鎖

我們直接上這種鎖的使用方法只搁,

- (void)otherTest
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}

// 線程1
// 刪除數(shù)組中的元素
- (void)__remove {
    pthread_mutex_lock(&_mutex);
    NSLog(@"__remove - begin");
    
    if (self.data.count == 0) {
        // 等待
        pthread_cond_wait(&_cond, &_mutex);
    }
    [self.data removeLastObject];
    NSLog(@"刪除了元素");
    
    pthread_mutex_unlock(&_mutex);
}

// 線程2
// 往數(shù)組中添加元素
- (void)__add {
    pthread_mutex_lock(&_mutex);
    
    sleep(1); 
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    
    // 信號(hào)
    pthread_cond_signal(&_cond);
    // 廣播
//    pthread_cond_broadcast(&_cond);
    
    pthread_mutex_unlock(&_mutex);
}

我們創(chuàng)建了兩個(gè)線程,一個(gè)往數(shù)組中添加數(shù)據(jù)妖滔,一個(gè)刪除數(shù)據(jù)隧哮,我們通過(guò)這個(gè)條件鎖實(shí)現(xiàn)的效果就是在數(shù)組中還沒(méi)有數(shù)據(jù)的時(shí)候等待,數(shù)組中添加了一個(gè)數(shù)據(jù)之后在進(jìn)行刪除座舍。

條件鎖就是互斥鎖+條件沮翔。我們聲明一個(gè)條件并初始化。

@property (assign, nonatomic) pthread_cond_t cond;
//使用完后也要pthread_cond_destroy(&_cond);
pthread_cond_init(&_cond, NULL);

__remove方法中

if (self.data.count == 0) {
    // 等待
    pthread_cond_wait(&_cond, &_mutex);
}

如果線程1率先拿到所并加鎖曲秉,執(zhí)行到上面代碼這里發(fā)現(xiàn)數(shù)組中還沒(méi)有數(shù)據(jù)采蚀,就執(zhí)行pthread_cond_wait,此時(shí)線程1會(huì)暫時(shí)放開(kāi)_mutex這個(gè)鎖承二,并在這休眠等待榆鼠。

線程2在__add方法中最開(kāi)始因?yàn)槟貌坏芥i,所以等待矢洲,在線程1休眠放開(kāi)鎖之后拿到鎖璧眠,加鎖,并執(zhí)行為數(shù)組添加數(shù)據(jù)的代碼读虏。添加完了之后會(huì)發(fā)個(gè)信號(hào)通知等待條件的線程责静,并解鎖。

    pthread_cond_signal(&_cond);
    
    pthread_mutex_unlock(&_mutex);

線程2執(zhí)行了pthread_cond_signal之后盖桥,線程1就收到了通知灾螃,退出休眠狀態(tài),繼續(xù)執(zhí)行下面的代碼揩徊。

這個(gè)地方可能有人會(huì)有疑問(wèn)腰鬼,是不是線程2應(yīng)該先unlock再cond_dingnal,其實(shí)這個(gè)地方順序沒(méi)有太大差別塑荒,因?yàn)榫€程2執(zhí)行了pthread_cond_signal之后熄赡,會(huì)繼續(xù)執(zhí)行unlock代碼,線程1收到signal通知后會(huì)推出休眠狀態(tài)齿税,同時(shí)線程1需要再一次持有這個(gè)鎖彼硫,就算此時(shí)線程2還沒(méi)有unlock,線程1等到線程2 unlock 的時(shí)間間隔很短,等到線程2 unlock 后線程1會(huì)再去持有這個(gè)鎖拧篮,并加鎖词渤。

NSCondition就是OC層面的條件鎖,內(nèi)部把mutex互斥鎖和條件封裝到了一起串绩。NSConditionLock其實(shí)也差不多缺虐,NSConditionLock可以指定具體的條件,這兩個(gè)OC層面的類的用法大家可以自行上網(wǎng)搜索礁凡。

dispatch_semaphore 信號(hào)量
@property (strong, nonatomic) dispatch_semaphore_t semaphore;
//初始化
self.semaphore = dispatch_semaphore_create(5);

在初始化一個(gè)信號(hào)的的過(guò)程中傳入dispatch_semaphore_create的值高氮,其實(shí)就代表了允許幾個(gè)線程同時(shí)訪問(wèn)。

再回到之前我們存錢(qián)取錢(qián)這個(gè)例子顷牌。

self.moneySemaphore = dispatch_semaphore_create(1);

我們一次只允許一個(gè)線程訪問(wèn)纫溃,所以在初始化的時(shí)候傳1。下面就是使用方法韧掩。

- (void)__drawMoney
{
    dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);
    
    // ... 省略代碼
    
    dispatch_semaphore_signal(self.moneySemaphore);
}

- (void)__saveMoney
{
    dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);
    
    // ... 省略代碼
    
    dispatch_semaphore_signal(self.moneySemaphore);
}

dispatch_semaphore_wait是怎么上鎖的呢?
如果信號(hào)量>0的時(shí)候窖铡,讓信號(hào)量-1疗锐,并繼續(xù)往下執(zhí)行。
如果信號(hào)量<=0的時(shí)候费彼,休眠等待滑臊。
就這么簡(jiǎn)單。

dispatch_semaphore_signal讓信號(hào)量+1箍铲。

小提示
在我們平時(shí)使用這種方法的時(shí)候雇卷,可以把信號(hào)量的代碼提取出來(lái)定義一個(gè)宏。

#define SemaphoreBegin \
static dispatch_semaphore_t semaphore; \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
    semaphore = dispatch_semaphore_create(1); \
}); \
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

#define SemaphoreEnd \
dispatch_semaphore_signal(semaphore);

讀寫(xiě)安全方案

上面我們講到的線程同步方案都是每次只允許一個(gè)線程訪問(wèn)颠猴,在實(shí)際的情況中关划,讀寫(xiě)的同步方案應(yīng)該下面這樣:

  1. 每次只能有一個(gè)線程寫(xiě)
  2. 可以有多個(gè)線程同時(shí)讀
  3. 讀和寫(xiě)不能同時(shí)進(jìn)行

這就是多讀單寫(xiě),用于文件讀寫(xiě)的操作翘瓮。在我們的iOS中可以用下面這兩種解決方案贮折。

pthread_rwlock 讀寫(xiě)鎖

這個(gè)讀寫(xiě)鎖的用法很簡(jiǎn)單,跟之前的普通互斥鎖都差不多资盅,大家隨便搜一下應(yīng)該就能搜到调榄,我就不拿出來(lái)寫(xiě)了,這里主要是提一下這種鎖呵扛,大家以后有需要的時(shí)候可以用每庆。

dispatch_barrier_async 異步柵欄

首先在使用這個(gè)函數(shù)的時(shí)候,我們要用自己創(chuàng)建的并發(fā)隊(duì)列今穿。
如果傳入的是一個(gè)串行隊(duì)列或者全局的并發(fā)隊(duì)列缤灵,那dispatch_barrier_async等同于dispatch_async的效果。

self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(self.queue, ^{
    [self read];
});
        
dispatch_barrier_async(self.queue, ^{
    [self write];
});

在讀取數(shù)據(jù)的時(shí)候,使用dispatch_async往對(duì)列中添加任務(wù)凤价,在寫(xiě)數(shù)據(jù)時(shí)鸽斟,用dispatch_barrier_async添加任務(wù)。

dispatch_barrier_async添加的任務(wù)會(huì)等前面所有的任務(wù)都執(zhí)行完利诺,他再執(zhí)行富蓄,而且他執(zhí)行的時(shí)候,不允許有別的任務(wù)同時(shí)執(zhí)行慢逾。

atomic

我們都知道這個(gè)atomic是原子性的意思立倍。他保證了屬性setter和getter的原子性操作,相當(dāng)于在set和get方法內(nèi)部加鎖侣滩。

atomic修飾的屬性是讀/寫(xiě)安全的口注,但不是線程安全。

假設(shè)有一個(gè) atomic 的屬性 "name"君珠,如果線程 A 調(diào)用 [self setName:@"A"]寝志,線程 B 調(diào)用 [self setName:@"B"],線程 C 調(diào)用 [self name]策添,那么所有這些不同線程上的操作都將依次順序執(zhí)行——也就是說(shuō)材部,如果一個(gè)線程正在執(zhí)行 getter/setter,其他線程就得等待唯竹。因此乐导,屬性 name 是讀/寫(xiě)安全的。

但是浸颓,如果有另一個(gè)線程 D 同時(shí)在調(diào)[name release]物臂,那可能就會(huì)crash,因?yàn)?release 不受 getter/setter 操作的限制产上。也就是說(shuō)棵磷,這個(gè)屬性只能說(shuō)是讀/寫(xiě)安全的,但并不是線程安全的晋涣,因?yàn)閯e的線程還能進(jìn)行讀寫(xiě)之外的其他操作泽本。線程安全需要開(kāi)發(fā)者自己來(lái)保證。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末姻僧,一起剝皮案震驚了整個(gè)濱河市规丽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌撇贺,老刑警劉巖赌莺,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異松嘶,居然都是意外死亡艘狭,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)巢音,“玉大人遵倦,你說(shuō)我怎么就攤上這事」俸常” “怎么了梧躺?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)傲绣。 經(jīng)常有香客問(wèn)我掠哥,道長(zhǎng),這世上最難降的妖魔是什么秃诵? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任续搀,我火速辦了婚禮,結(jié)果婚禮上菠净,老公的妹妹穿的比我還像新娘禁舷。我一直安慰自己,他們只是感情好毅往,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布榛了。 她就那樣靜靜地躺著,像睡著了一般煞抬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上构哺,一...
    開(kāi)封第一講書(shū)人閱讀 52,682評(píng)論 1 312
  • 那天革答,我揣著相機(jī)與錄音,去河邊找鬼曙强。 笑死残拐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的碟嘴。 我是一名探鬼主播溪食,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼娜扇!你這毒婦竟也來(lái)了错沃?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤雀瓢,失蹤者是張志新(化名)和其女友劉穎枢析,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體刃麸,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡醒叁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片把沼。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡啊易,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出饮睬,到底是詐尸還是另有隱情租谈,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布续捂,位于F島的核電站垦垂,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏牙瓢。R本人自食惡果不足惜劫拗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望矾克。 院中可真熱鬧页慷,春花似錦、人聲如沸胁附。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)控妻。三九已至州袒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間弓候,已是汗流浹背郎哭。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留菇存,地道東北人夸研。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像依鸥,于是被迫代替她去往敵國(guó)和親亥至。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361