02Python學習筆記之二.九【線程】2019-08-24

章節號 內容????????????
1圖片格式(png) 寬度大于620px,保持高寬比減低為620px
12 123
1-1-1 方法
1-1-1 方法

第1章節?線程

  • 1-1?線程—多線程執行(直接使用threading.Thread)

??導入threading模塊即可。

  1 import threading
  2 import time
  3 
  4 def pri():
  5     print("thread run")
  6     time.sleep(1)
  7 
  8 for i in range(5):
  9     t = threading.Thread(target=pri)
 10     t.start()

li@li-ThinkPad-T420s:~$ python3 15.py 
thread run
thread run
thread run
thread run
thread run

??線程也是多任務的一種方式。
??程序就是代碼。進程是開始執行的代碼,它擁有部分系統分配給它的資源。
??每個進程都有一個主線程。
??主線程會等待子線程結束才退出。

  • 1-2?線程—使用Thread子類完成多線程

??子線程執行同一個函數,互相不會干擾。但是通常不同線程執行的是不同函數。

import threading
import time

class mythr(threading.Thread):
    def run(self):
        for i in range(5):
            time.sleep(1)
#self.name當前線程的名字
            msg = self.name+","+str(i)
            print(msg)
        
t=mythr()
t.start()
Thread-6,0
Thread-6,1
Thread-6,2
Thread-6,3
Thread-6,4

??子結束,父未回收,則子成為僵尸
??父結束,子還存活,則子成為孤兒

li@li-System-Product-Name:~$ ps -aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0 225720  9480 ?        Ss   15:28   0:01 /sbin/init splash
root         2  0.0  0.0      0     0 ?        S    15:28   0:00 [kthreadd]
root         4  0.0  0.0      0     0 ?        I<   15:28   0:00 [kworker/0:0H]
root         6  0.0  0.0      0     0 ?        I<   15:28   0:00 [mm_percpu_wq]
root         7  0.0  0.0      0     0 ?        S    15:28   0:00 [ksoftirqd/0]
root         8  0.0  0.0      0     0 ?        I    15:28   0:00 [rcu_sched]
root         9  0.0  0.0      0     0 ?        I    15:28   0:00 [rcu_bh]
root        10  0.0  0.0      0     0 ?        S    15:28   0:00 [migration/0]
root        11  0.0  0.0      0     0 ?        S    15:28   0:00 [watchdog/0]
root        12  0.0  0.0      0     0 ?        S    15:28   0:00 [cpuhp/0]
root        13  0.0  0.0      0     0 ?        S    15:28   0:00 [cpuhp/1]
root        14  0.0  0.0      0     0 ?        S    15:28   0:00 [watchdog/1]
root        15  0.0  0.0      0     0 ?        S    15:28   0:00 [migration/1]
root        16  0.0  0.0      0     0 ?        S    15:28   0:00 [ksoftirqd/1]
root        17  0.0  0.0      0     0 ?        I    15:28   0:00 [kworker/1:0]
root        18  0.0  0.0      0     0 ?        I<   15:28   0:00 [kworker/1:0H]
root        19  0.0  0.0      0     0 ?        S    15:28   0:00 [cpuhp/2]
root        20  0.0  0.0      0     0 ?        S    15:28   0:00 [watchdog/2]
root        21  0.0  0.0      0     0 ?        S    15:28   0:00 [migration/2]
root        22  0.0  0.0      0     0 ?        S    15:28   0:00 [ksoftirqd/2]
root        23  0.0  0.0      0     0 ?        I    15:28   0:00 [kworker/2:0]
root        24  0.0  0.0      0     0 ?        I<   15:28   0:00 [kworker/2:0H]
root        25  0.0  0.0      0     0 ?        S    15:28   0:00 [cpuhp/3]
.
.
.

??PID為0的進程,負責進程間調度。
??PID為1的進程,負責間接或簡介創建其他的進程。
??PID為1的進程,處理所有孤兒進程。

  • 1-3?線程—線程的執行順序

??主進程和子進程執行順序是不一定的,由操作系統的調度來決定。

import threading
import time

class mythr(threading.Thread):
    def run(self):
        for i in range(5):
            time.sleep(1)
            msg = self.name+","+str(i)
            print(msg)
            
def test():
#生成5個線程,都從run()開始執行
    for i in range(5):
        print("test"+str(i))
        t = mythr()
        t.start()

test()
test0
test1
test2
test3
test4
Thread-6,0
Thread-7,0
Thread-8,0
Thread-9,0
Thread-10,0
Thread-6,1
Thread-7,1
Thread-8,1
Thread-9,1
Thread-10,1
Thread-6,2
Thread-7,2
Thread-8,2
Thread-10,2
Thread-9,2
Thread-6,3
Thread-7,3
Thread-8,3
Thread-10,3
Thread-9,3
Thread-6,4
Thread-7,4
Thread-10,4
Thread-8,4
Thread-9,4

??第一次執行有順序(創建有順序),但是一旦sleep后,先后就不確定了。

  • 1-4?線程—線程共享全局變量

import threading
import time

g_num=0

def w1():
    global g_num
    for i in range(3):
        g_num+=1
    print("w1 num is %d"%g_num)

def w2():
    global g_num
    print("w2 num is %d"%g_num)

print("before thread,num is %d"%g_num)
t1=threading.Thread(target=w1)
t1.start()
#延時保證t1執行完畢
time.sleep(1)

t2=threading.Thread(target=w2)
t2.start()
before thread,num is 0
w1 num is 3
w2 num is 3

??↑這里說明一個問題,線程間,全局變量是共用的。
??上述代碼是刻意避免了多個線程同時訪問一個變量,因為這樣會導致一些問題。


??↑程間,數據和代碼均分用,互不干涉。
??↑程間,數據公用,代碼分用,數據可以共享。
??利弊各自參半。線程共享的問題如下↓

  1 from threading import Thread
  2 
  3 g_num = 0
  4 
  5 def test1():
  6     global g_num
  7     for i in range(1000000):
  8         g_num += 1
  9     print("test1 %d" % g_num)
 10 
 11 
 12 def test2():
 13     global g_num
 14     for i in range(1000000):
 15         g_num += 1
 16     print("test2 %d" % g_num)
 17 
 18 
 19 t1 = Thread(target=test1)
 20 t1.start()
 21 
 22 t2 = Thread(target=test2)
 23 t2.start()
 24 
 25 print("final %d"%g_num)

li@li-ThinkPad-T420s:~$ python3 16.py 
test1 1213667
final 1260000
test2 1283779

??↑兩個線程,各自對同一個全局變量進行自加1至100萬,最后結果按道理來說,應該是200萬。實際結果如上。

??原因分析: g_num += 1是關鍵。這里在視覺上是一行代碼,但是CPU實際調度過程中卻至少需要2個單位時間來運行,首先是執行g_num + 1,然后再執行g_num=g_num + 1。如果CPU在連續的兩個時間片,執行的都是不同線程的加法操作或者賦值操作,那么實際運行結果就會和預想產生出入。

??↓我們稍微修改一下代碼,讓t1線程有足夠的時間來運行。看結果如何:

from threading import Thread
import time
g_num = 0

def test1():
    global g_num
    for i in range(1000000):
        g_num += 1
    print("test1 %d" % g_num)


def test2():
    global g_num
    for i in range(1000000):
        g_num += 1
    print("test2 %d" % g_num)


t1 = Thread(target=test1)
t1.start()
#讓主線程等t1線程3秒鐘
time.sleep(3)
t2 = Thread(target=test2)
t2.start()

print("final %d"%g_num)

li@li-System-Product-Name:~$ python3 1.py
test1 1000000
final 1051813
test2 2000000

??↑可以看到計算的記過已經符合預期了,但是最后打印的final的值為什么是這個呢?因為final是由主線程執行的,而主線程和t2線程是同時執行的,相當于兩條腿同時跑。那如何才能把final 的輸出也變成預期的值↓

from threading import Thread
import time
g_num = 0

def test1():
    global g_num
    for i in range(1000000):
        g_num += 1
    print("test1 %d" % g_num)


def test2():
    global g_num
    for i in range(1000000):
        g_num += 1
    print("test2 %d" % g_num)


t1 = Thread(target=test1)
t1.start()
time.sleep(3)
t2 = Thread(target=test2)
t2.start()
#讓主線程等t2線程也加完畢
time.sleep(3)
print("final %d"%g_num)

li@li-System-Product-Name:~$ python3 1.py
test1 1000000
test2 2000000
final 2000000

  • 1-5?線程—列表當做參數傳遞到線程處理函數中

from threading import Thread
import time
nums = [11, 22, 33]


def test1(nums):
    nums.append(44)
    print("test1 nums", nums)


def test2(nums):
#等待t1進行添加44的操作,確保它執行完畢
    time.sleep(1)
    print("test2 get nums", nums)


t1 = Thread(target=test1, args=(nums,))
t1.start()

t2 = Thread(target=test2, args=(nums,))
t2.start()


print("final nums", nums)
li@li-System-Product-Name:~$ python3 2.py
test1 nums [11, 22, 33, 44]
final nums [11, 22, 33, 44]
test2 get nums [11, 22, 33, 44]



??↑列表是可變數據類型,可以直接進行引用修改。

  • 1-6?線程—避免全局變量被錯誤的使用

??上述的示例代碼中,因為不同的線程并行執行,對全局變量產生了不符合預期的結果。關鍵就是一句核心的代碼g_num += 1被拆分執行了。
??那么我們應該如何避免這種問題的發生?應該有那么幾種方式:
??1、讓各個線程在時間上串行執行(最笨,也是背離多任務初衷的方式,就是上述的sleep)
??2、讓一個線程不斷的等另一個線程執行完畢(輪詢)
??3、讓關鍵語句g_num += 1強制不拆分執行(加互斥鎖)


??解決:2、讓一個線程不斷的等(這個等是耗費資源的等)另一個線程執行完畢,效率低下↓
??首先設置一個全部的標志g_f,在兩個線程中分別對這個標志進行判斷,不管標志為什么值,肯定有一個線程是不滿足條件而不運行的。
??然后在不滿足條件的線程中,使用while死循環來查看標志g_f是否符合自己的運行條件,是就運行,不是就繼續循環。記住要設置條件跳出while死循環。

from threading import Thread
import time
g_num = 0
g_f =0
def test1():
    global g_num
    global g_f
    if g_f == 0:
        for i in range(1000000):
            g_num += 1
        g_f=1
        print("test1 %d" % g_num)


def test2():
    global g_num
    global g_f
    #不停的判斷,為輪詢
    while True:
        if g_f==1:   
            for i in range(1000000):
                g_num += 1
    #加完直接退出循環
            break
    print("test2 %d" % g_num)


t1 = Thread(target=test1)
t1.start()

t2 = Thread(target=test2)
t2.start()
print("final %d"%g_num)

可以看到,全程都未用sleep函數
final 110183
test1 1000000
test2 2000000
  • 1-7?線程—互斥鎖

??上述的例子中,用while True的方式等待,是很不經濟的做法。有沒有什么好的解決方式?有的,那就是互斥鎖。
??使用方式:導入Lock庫,用lo=Lock()創建鎖,在多個線程的核心代碼前后加入lo.acquire()和lo.release(),一旦有一個線程上鎖成功,那么其他的鎖都會堵塞(一直等待)到這個鎖解開為止,期間不占CPU。

from threading import Thread, Lock
import time
g_num = 0

def test1():
    global g_num
    #兩個線程都搶上鎖,因為你不知道哪個線程先執行,但是只要某一個線程成功上鎖,另一個線程就會在上鎖處卡住等待這個鎖解開
    lo.acquire()
    for i in range(1000000):
        g_num += 1
    #兩個線程都要解鎖
    lo.release()
    print("test1 %d" % g_num)


def test2():
    global g_num
    #兩個線程都搶上鎖,因為你不知道哪個線程先執行,但是只要某一個線程成功上鎖,另一個線程就會在上鎖處卡住等待這個鎖解開
    lo.acquire()
    for i in range(1000000):
        g_num += 1
    #兩個線程都要解鎖
    lo.release()
    print("test2 %d" % g_num)

#創建一把鎖,這把鎖只能上一次,一旦被鎖就不能鎖第二次
lo=Lock()

t1 = Thread(target=test1)
t1.start()

t2 = Thread(target=test2)
t2.start()
print("final %d"%g_num)
test1 1000000
test2 2000000
final 215425
  • 1-7?線程—幾個問題

??上面的例子中,有這么一個問題:我們加了鎖,讓一個線程從頭到位執行完100萬次加法,才解鎖,也就是說我們把一個多任務人為寫成了單任務,這是違背多任務的初衷的,那么如何解決這個問題呢?那就是把加鎖解鎖放到for循環內部去。這也是一個上鎖的原則:上鎖的代碼部分要最小化

from threading import Thread, Lock
import time
g_num = 0

def test1():
    global g_num
    for i in range(1000000):
        lo.acquire()
        g_num += 1
        lo.release()
    print("test1 %d" % g_num)


def test2():
    global g_num
    for i in range(1000000):
        lo.acquire()
        g_num += 1
        lo.release()
    print("test2 %d" % g_num)

#創建一把鎖,這把鎖只能上一次,一旦被鎖就不能鎖第二次

lo=Lock()



t1 = Thread(target=test1)
t1.start()

t2 = Thread(target=test2)
t2.start()
print("final %d"%g_num)
final 24696
test1 1993796
test2 2000000

??↑但是這也有取舍的問題,在運行過程中,明顯感覺運行速度比上鎖在for循環外慢,個人覺得這是頻繁的上鎖解鎖造成的系統額外開銷。
??解鎖后相當于是通知其他鎖,所以造成系統開銷不大

  • 1-7?線程—使用非共享的變量

??不修改全局變量,不加鎖。

from threading import Thread, Lock
import time

def test1():
    num =100
    num+=1
    print("test1 %d" % num,end="\n")
    time.sleep(1)
    print("test1 %d" % num,end="\n")

def test2():
    num =100
    print("test2 %d" % num)


t1 = Thread(target=test1)
t1.start()

t2 = Thread(target=test2)
t2.start()
test1 101
test2 100
test1 101

??↑上例代碼說明:不同的線程執行不同的函數,修改局部變量,結果是互不影響的。那么,如果兩個線程,執行的是同一個函數呢??????

from threading import Thread, Lock
#導入threading是為了使用threading.current_thread().name
import threading
import time

def test1():
    print("now thread is %s"%threading.current_thread().name,end="**")

t1 = Thread(target=test1)
t1.start()

t2 = Thread(target=test1)
t2.start()
now thread is Thread-7now thread is Thread-6****li@li-System-Product-Name:~/Documents$ 

??↑首先我們把代碼改成如上所示,先測試在當前的環境下,線程的名字各是什么,方便之后使用,這里看到了是Thread-6和Thread-7(這個因電腦環境而異,不一定相同),下面我們改寫代碼:

from threading import Thread, Lock
#導入threading是為了使用threading.current_thread().name
import threading
import time

def test1():
    print("now thread is %s ##"%threading.current_thread().name,end="**")
    num =100
    if threading.current_thread().name == "Thread-6":
        num+=1
    else:
        time.sleep(2)
    print("now thread is %s,num is %d"%(threading.current_thread().name,num))


t1 = Thread(target=test1)
t1.start()

t2 = Thread(target=test1)
t2.start()
now thread is Thread-6 ##**now thread is Thread-7 ##now thread is Thread-6,num is 101**
now thread is Thread-7,num is 100

??↑首先兩個線程都進入test1()函數執行,其中Thread-6進行加1操作,然后打印num值,Thread-7直接睡2秒,再打印num值,可以看到,各自打印的num值不同。
??小tip,注意“**”的位置,來分析線程和end=執行的關系。
??這得出了一個結論,線程對局部變量的修改,也是各自獨有的,并不共享。
??總結:
??1、多進程中,全局和局部的變量,都是獨享的。
??2、多線程中,全局變量共享,局部變量獨享。
??所以,線程的局部變量,不用加鎖,即便是多線程運行同一個函數。

  • 1-8?線程—死鎖

??我等著你解鎖,你等著我解鎖,就產生了死鎖。

import threading
import time

class thr1(threading.Thread):
    def run(self):
        if lo1.acquire():
            print(self.name+"is start")
            time.sleep(1)
            if lo2.acquire():
                print(self.name+"end")
                lo2.release()
            lo1.release()

class thr2(threading.Thread):
    def run(self):
        if lo2.acquire():
            print(self.name+"is start")
            time.sleep(1)
            if lo1.acquire():
                print(self.name+"end")
                lo1.release()
            lo2.release()

#產生2把鎖
lo1=threading.Lock()
lo2=threading.Lock()

t1=thr1()
t2=thr2()
t1.start()
t2.start()


??如何防止死鎖呢?
??1、編寫程序的時候使用銀行家算法!
??2、添加超時的時間!
??lo.acquire()有個參數,如果blocking為True,則會堵塞。如果blocking為False,則不堵塞。

lo.acquire([blocking])
In [4]: ?a.acquire
Docstring:
acquire(blocking=True, timeout=-1) -> bool
(acquire_lock() is an obsolete synonym)

Lock the lock.  Without argument, this blocks if the lock is already
locked (even by the same thread), waiting for another thread to release
the lock, and return True once the lock is acquired.
With an argument, this will only block if the argument is true,
and the return value reflects whether the lock is acquired.
The blocking operation is interruptible.
Type:      builtin_function_or_method

??timeout=-1表示永不休止。
??下面我們加入超時時間,代碼如下:

import threading
import time

class thr1(threading.Thread):
    def run(self):
        if lo1.acquire():
            print(self.name+"is start1")
            time.sleep(1)
            if lo2.acquire(timeout=3):
                print(self.name+"end")
                lo2.release()
            lo1.release()

class thr2(threading.Thread):
    def run(self):
        if lo2.acquire():
            print(self.name+"is start")
            time.sleep(1)
            if lo1.acquire(timeout=3):
                print(self.name+"end")
                lo1.release()
            lo2.release()



#產生2把鎖
lo1=threading.Lock()
lo2=threading.Lock()

t1=thr1()
t2=thr2()
t1.start()
t2.start()
li@li-System-Product-Name:~$ python3 3.py
Thread-1is start1
Thread-2is start
Thread-1end
li@li-System-Product-Name:~$ python3 3.py
Thread-1is start1
Thread-2is start
Thread-2end
li@li-System-Product-Name:~$ python3 3.py
Thread-1is start1
Thread-2is start
li@li-System-Product-Name:~$ 

??↑可以看到,多次運行的結果是不一樣的,因為多線程調度后,lo1和lo2誰先被解鎖是不一定的。注意到還有一個end都沒有的情況。思考為什么會有這樣的情況。

  • 1-9?線程—同步

??同步就是協同步調,按照預定的先后或次序運行。
??異步就是誰先執行不確定。

from threading import Thread, Lock


import time

class thr1(Thread):
    def run(self):
        while True:
            if lo1.acquire():
                print("thr1")
                time.sleep(0.5)
                lo2.release()

class thr2(Thread):
    def run(self):
        while True:
            if lo2.acquire():
                print("thr2")
                time.sleep(0.5)
                lo3.release()

class thr3(Thread):
    def run(self):
        while True:
            if lo3.acquire():
                print("thr3")
                time.sleep(0.5)
                lo1.release()

lo1=Lock()

lo2=Lock()
lo2.acquire()

lo3=Lock()
lo3.acquire()

t1=thr1()
t2=thr2()
t3=thr3()

t1.start()
t2.start()
t3.start()
thr1
thr2
thr3
thr1
thr2
thr3
thr1
thr2
thr3

??↑三個線程中,循環上解鎖!這些都是前人的思想結晶,要好好吸收消化。
??有規律,這里就是同步~

  • 1-10?線程—解決耦合

??生產者與消費者模式:

from queue import Queue
import threading
import time

class Ser(threading.Thread):
    def run(self):
        global queue
        count =0
        while True:
            if queue.qsize()<1000:
                for i in range(100):
                    count=count+1
                    msg = self.name+"made stuff"+str(count)
                    queue.put(msg)
                    print(msg)
            time.sleep(0.5)

class Xer(threading.Thread):
    def run(self):
        global queue
        count =0
        while True:
            if queue.qsize()>100:
                for i in range(3):
                    count=count+1
                    msg = self.name+"  use  "+queue.get()
                    print(msg)
            time.sleep(1)

queue=Queue()
for i in range(500):
    print("init stuff"+str(i))
    queue.put("init stuff"+str(i))
for i in range(2):
    p=Ser()
    p.start()
for i in range(5):
    x=Xer()
    x.start()

??結果請自行運行查看。
??在編程過程中,要解決生成和處理之間速度不統一的問題,可以使用隊列queue來完成。縱觀很多設計模式,都會使用第三者來解耦。
??或者可以用讀寫文件方式來完成。
??創業公司,買一個虛擬服務器,完成所有功能。但是規模擴大后,要把功能分散到多個服務器上,多個服務器間還要相互通信,這時候就體現出你程序的解耦性能了。

  • 1-11?線程—ThreadLocal

↑↓

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

推薦閱讀更多精彩內容