D29:多线程

目录

一. 多线程的概念(程序, 进程, 线程)

二. 为什么使用多线程

三. 怎样创建线程(2种方法)

四. 怎样监听线程的结束, 取消线程

五. 线程的优先级

六. 线程的同步锁

  1. MyAccount类
  2. ViewController.m(此时执行就会出现取款超出余额的问题, 需要添加同步锁)
  3. 第一种添加线程锁的方法(在MyAccount类中的- (void)withDraw:(float)tmpMoney方法中用@synchronized(self)添加锁)
  4. 第二种添加线程锁的方法(设一个成员变量:NSLock的对象)

七. NSOperation创建线程

  1. NSInvocationOperation
  2. NSBlockOperation
  3. 自定义NSBlockOperation的子类

八. Block

  1. 没有返回值的block
  2. 有返回值, 返回值类型是基本类型(int为例)
  3. 有返回值, 返回值类型是基本类型(int为例)
  4. 测试

一. 多线程的概念

程序: 一段代码, 是一个静态的文件
进程: 一个运行起来的程序, 进程实惠占用内存的
线程: 进程的组成部分, 一个进程至少需要一个线程, 是进程处理逻辑的基本单元, iOS程序运行起来后, 默认创建一个主线程, 自动维护这个主线程; 如果需要使用多线程, 必须自己手动创建和维护


二. 为什么使用多线程

将下载或者数据库操作等放在了主线程里面, 会阻塞主线程, 造成一种假死的现象
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    UIButton *button1 = [MyUtility createButtonWithFrame:CGRectMake(100, 100, 80, 40) title:@"下载数据" backgroundImageName:nil target:self action:@selector(downloadAction)];
    [self.view addSubview:button1];
    
    // 按钮2: 点击打印一条信息
    UIButton *button2 = [MyUtility createButtonWithFrame:CGRectMake(100, 200, 80, 40) title:@"点击" backgroundImageName:nil target:self action:@selector(clickButton)];
    [self.view addSubview:button2];
}

- (void)downloadAction
{
    NSLog(@"%s", __func__);
}

- (void)clickButton
{
    // 让当前的线程睡眠
    // 模拟下载数据需要的时间
    [NSThread sleepForTimeInterval:10];
    
    // 将下载放在了主线程里面, 会阻塞主线程
    // 造成了一种假死的现象
    
    // 类似下载或者数据库操作的逻辑, 占用时间加长, 需要在主线程以外的线程中去处理
 
    NSLog(@"%s", __func__);
}

三. 怎样创建线程

1. 使用NSThread来创建线程1
// 1. 点击按钮, 创建一个线程
UIButton *btn1 = [MyUtility createButtonWithFrame:CGRectMake(100, 100, 80, 40) title:@"创建线程1" backgroundImageName:nil target:self action:@selector(createThreadOne)];
[self.view addSubview:btn1];

// 创建线程1  
- (void)createThreadOne
{
    // detachNewThreadSelector:<#(SEL)#> toTarget:<#(id)#> withObject:<#(id)#>
    /*
     创建了一个线程, 同时将线程启动
     第一个参数: 线程的执行体方法
     第二个参数: 线程执行体方法所属的对象
     第三个参数: 线程的执行体方法的参数
     */
    NSNumber *n = @100;
    [NSThread detachNewThreadSelector:@selector(threadOne:) toTarget:self withObject:n];
}

// 线程1的执行体方法
- (void)threadOne:(NSNumber *)n
{
    for (int i = 0; i < n.intValue; i++) {
        NSLog(@"线程1:%d", i);
        
        // 让当前线程睡眠
        [NSThread sleepForTimeInterval:5];
    }
}
2. 使用NSThread来创建线程2
// 2. 创建线程的第二种方式
UIButton *btn2 = [MyUtility createButtonWithFrame:CGRectMake(100, 200, 80, 40) title:@"创建线程2" backgroundImageName:nil target:self action:@selector(createThreadTwo)];
[self.view addSubview:btn2];
NSLog(@"%@", [NSThread currentThread].name);

- (void)createThreadTwo
{
    /*
     创建了一个线程, 线程没有自动启动
     第一个参数: 线程执行体方法所属的对象
     第二个参数: 线程的执行体方法
     第三个参数: 线程的执行体方法的参数
     */
    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadTwo) object:nil];
    // 设置线程的名字
    thread2.name = @"下载线程";
    // 手动启动线程
    [thread2 start];
}

// 线程2的执行体方法
- (void)threadTwo
{
    for (int i = 0; i < 100; i++) {
        // 获取当前的线程对象
        NSThread *currentThread = [NSThread currentThread];
        NSLog(@"%@ %d", currentThread.name, i+1);
    }
}

四. 怎样监听线程的结束, 取消线程

1. 第1种取消线程方法
#import "ViewController.h"

@interface ViewController ()
{
    // 线程2是否取消
    BOOL _isThreadTwoCancel;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    // 创建2个线程, 并启动
    // 第一个线程执行for循环, 第二个线程执行死循环
    // 第一个线程结束后取消第二个线程
    // 程序接收到第二个线程结束的信息后, 停止第二个线程的执行
    
    // 创建第一个线程
    [NSThread detachNewThreadSelector:@selector(threadOne) toTarget:self withObject:nil];
    
    // 创建第二个线程
    NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadTwo) object:nil];
    [t2 start];
}

- (void)threadOne
{
    for (int i = 0; i < 1000; i++) {
        NSLog(@"执行线程1");
        
        if (i == 999) {
            NSLog(@"线程1执行完毕");
            // 取消线程2
            _isThreadTwoCancel = YES;
        }
    }
    
}

- (void)threadTwo
{
    
    int i = 0;

    while (true) {
        
        // 退出线程的方法需要放到循环里面
        if (_isThreadTwoCancel) {
            // 结束当前的线程
            [NSThread exit];
        }
        NSLog(@"执行线程2: %d", i+1);
        i++;
    }
}
2. 第2种取消线程方法
@interface ViewController ()
{
    …………………………………………………………………………………………
    
    NSThread *_t2;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    …………………………………………………………………………………………
            
    // 创建第二个线程
//    NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadTwo) object:nil];
//    [t2 start];

    _t2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadTwo) object:nil];
    [_t2 start];
}

- (void)threadOne
{
    for (int i = 0; i < 1000; i++) {
        NSLog(@"执行线程1");
        
//        if (i == 999) {
//            NSLog(@"线程1执行完毕");
//            // 取消线程2
//            _isThreadTwoCancel = YES;
//        }
        if (i == 999) {
            NSLog(@"线程1执行完毕");
            // 取消线程2
            [_t2 cancel];
        }
    }
}

- (void)threadTwo
{
    
    int i = 0;

    while (true) {        
        if ([_t2 isCancelled]) {
            
            // 结束当前的线程
            [NSThread exit];
        }
        
        NSLog(@"执行线程2: %d", i+1);
        i++;
    }
}  
3. 通过通知中心监听线程的结束(给线程1和线程2先设置name属性)
- (void)viewDidLoad {
    [super viewDidLoad];
    …………………………………………………………………………………………
            
    _t2.name = @"II";
    
    // 监听线程是否结束
    // NSThreadWillExitNotification
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(threadWillExit:) name:NSThreadWillExitNotification object:nil];
}

// 在线程结束的时候执行一些操作
- (void)threadWillExit:(NSNotification *)n
{
    NSThread *t = [n object];
    NSLog(@"线程%@结束", t.name);
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)threadOne
{
    [NSThread currentThread].name = @"I";
    
    …………………………………………………………………………………………
}

五. 线程的优先级

每个线程创建之后都有优先级
优先级在0-1之间, 值越大, 优先级越高
默认优先级: 0.5
优先级高的线程执行的机会更大

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    // 每个线程创建之后都有优先级
    // 优先级在0-1之间, 值越大, 优先级越高
    // 默认优先级: 0.5
    // 优先级高的线程执行的机会更大
    NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(threadOne) object:nil];
    t1.name = @"线程I";
    [t1 start];
    t1.threadPriority = 0;
    
    NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadTwo) object:nil];
    t2.name = @"线程II";
    [t2 start];
    t2.threadPriority = 1;
}

- (void)threadOne
{
    for (int i = 0; i < 1000; i++) {
        NSLog(@"执行了%@: %d", [NSThread currentThread].name, i);
    }
}

- (void)threadTwo
{
    for (int i = 0; i < 1000; i++) {
        NSLog(@"执行了%@: %d", [NSThread currentThread].name, i);
    }
}

六. 线程的同步锁

模拟银行账户取钱
创建一个账户对象, 模拟同时有2个人取钱

1. MyAccount类
#import <Foundation/Foundation.h>

@interface MyAccount : NSObject

/*
 accountNo: 账户的号码
 money:     账户的余额
 */
- (instancetype)initWithAccountNo:(NSString *)accountNo money:(float)money;
- (void)withDraw:(float)tmpMoney;

@end  

#import "MyAccount.h"

@implementation MyAccount
{
    // 账户的号码
    NSString *_accountNo;
    // 余额
    float _money;
}

- (instancetype)initWithAccountNo:(NSString *)accountNo money:(float)money
{
    self = [super init];
    if (self) {
        // 给成员变量赋值
        _accountNo = accountNo;
        _money = money;
    }
    return self;
}

// 取钱的操作
- (void)withDraw:(float)tmpMoney
{
    if (_money >= tmpMoney) {
        
        // 模拟取钱操作时需要一些时间
        [NSThread sleepForTimeInterval:0.01];
        
        // 取钱
        _money -= tmpMoney;
        
        NSLog(@"%@取了%f元钱", [NSThread currentThread].name, tmpMoney);
    } else {
        NSLog(@"余额不足");
    }
}

@end
2. ViewController.m(此时执行就会出现取款超出余额的问题, 需要添加同步锁)
#import "ViewController.h"
#import "MyAccount.h"

// 系统还有NSCondition 实现多线程的方式


@interface ViewController ()
{
    // 公共的账户
    MyAccount *_account;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    // 创建一个账户
    _account = [[MyAccount alloc] initWithAccountNo:@"Yuen" money:9000000];
    
    // 创建两个线程
    NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(threadOne) object:nil];
    [t1 start];
    
    NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadTwo) object:nil];
    [t2 start];
}

- (void)threadOne
{
    [NSThread currentThread].name = @"Yank";
    [_account withDraw:8000000];
}

- (void)threadTwo
{
    [NSThread currentThread].name = @"Alfred";
    [_account withDraw:2000000];
}

@end  
3. 第一种添加线程锁的方法(在MyAccount类中的- (void)withDraw:(float)tmpMoney方法中用@synchronized(self)添加锁)
- (void)withDraw:(float)tmpMoney
{
    // @synchronized(self)添加锁
    // 保证_money成员变量在同一时刻只有一个线程修改
    
    @synchronized(self) {
        
        if (_money >= tmpMoney) {
            
            // 模拟取钱操作时需要一些时间
            [NSThread sleepForTimeInterval:0.01];
            
            // 取钱
            _money -= tmpMoney;
            
            NSLog(@"%@取了%f元钱", [NSThread currentThread].name, tmpMoney);
        } else {
            NSLog(@"余额不足");
        }
    }
}
4. 第二种添加线程锁的方法(设一个成员变量:NSLock的对象)
#import "MyAccount.h"

@implementation MyAccount
{
    …………………………………………………………………………………………
        
    // 线程锁
    NSLock *_lock;
}

- (instancetype)initWithAccountNo:(NSString *)accountNo money:(float)money
{
    self = [super init];
    if (self) {
        …………………………………………………………………………………………        
        
        // 初始化线程锁
        _lock = [[NSLock alloc] init];
    }
    return self;
}

// 取钱的操作
- (void)withDraw:(float)tmpMoney
{
    [_lock lock];
    
    …………………………………………………………………………………………
        
    // 释放锁
    [_lock unlock];
}

七. NSOperation创建线程

1. NSInvocationOperation
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    // 创建线程的队列
    _queue = [[NSOperationQueue alloc] init];
    
    // 1. NSInvoationOperation
    /*
     第一个参数: 线程的执行体由哪个对象执行
     第二个参数: 线程的执行体对象的方法
     第三个参数: 线程的执行体方法需要传递的实参
     */
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(threadOne) object:nil];
    
    // 线程执行完成后调用的block
    [op1 setCompletionBlock:^{
        NSLog(@"线程I执行完成");
    }];
    
    // 把线程添加到队列中
    [_queue addOperation:op1];
}

- (void)threadOne
{
    for (int i = 0; i < 100; i++) {
        NSLog(@"线程一:%d", i);
    }

}

- (void)dealloc
{
    // 取消所有线程
    [_queue cancelAllOperations];
}
2. NSBlockOperation
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    // 创建线程的队列
    _queue = [[NSOperationQueue alloc] init];
    
    // 2. NSBlockOperation
    // 参数是一个代码块, 是线程的执行体
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 100; i++) {
            NSLog(@"执行了线程II:%d", i);
        }
    }];
    
    // 线程执行完成后调用的block
    [op1 setCompletionBlock:^{
        NSLog(@"线程II执行完成");
    }];
    
    [_queue addOperation:op2];
}

- (void)dealloc
{
    // 取消所有线程
    [_queue cancelAllOperations];
}
3. 自定义NSBlockOperation的子类
  1. ImageOperation类

     #import <Foundation/Foundation.h>
     #import <UIKit/UIKit.h>
     
     @interface ImageOperation : NSOperation
     
     // 图片的URL字符串
     @property (nonatomic, strong) NSString *urlString;
     // 图片视图对象
     @property (nonatomic, strong) UIImageView *imageView;
     
     @end
       
     #import "ImageOperation.h"
     
     @implementation ImageOperation
     
     // 自定义NSOperation类型, 需要实现main方法
     // 这个方法是线程的执行体
     - (void)main
     {
         NSURL *url = [NSURL URLWithString:self.urlString];
         
         NSData *data = [NSData dataWithContentsOfURL:url];
         
         // 在主线程刷新UI
         [self performSelectorOnMainThread:@selector(refreshUI:) withObject:data waitUntilDone:YES];
     }
     
     - (void)refreshUI:(NSData *)data
     {
         self.imageView.image = [UIImage imageWithData:data];
     }
     
     @end  
    
  2. ViewController.m

     #import "ViewController.h"
     #import "ImageOperation.h"
     
     @interface ViewController ()
     {
         NSOperationQueue *_queue;
         UIImageView *_myImageView;
     }
     
     @end
     
     @implementation ViewController
     
     - (void)viewDidLoad {
         [super viewDidLoad];
         // Do any additional setup after loading the view, typically from a nib.
         
         // 创建线程的队列
         _queue = [[NSOperationQueue alloc] init];
     
         _myImageView = [[UIImageView alloc] initWithFrame:CGRectMake(50, 100, 240, 320)];
         [self.view addSubview:_myImageView];
         
         // 第三种创建线程的方式
         // 创建一个线程, 下载一张图片, 显示到视图上
         
         UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
         btn.frame = CGRectMake(100, 40, 80, 40);
         [btn setTitle:@"下载" forState:UIControlStateNormal];
         [self.view addSubview:btn];
         [btn addTarget:self action:@selector(downloadAction) forControlEvents:UIControlEventTouchUpInside];
     
     }
     
     - (void)downloadAction
     {
         // 创建线程下载图片
         ImageOperation *op = [[ImageOperation alloc] init];
         // 图片的URL
         op.urlString = @"http://img3.3lian.com/2006/027/08/007.jpg";
         // 图片视图
         op.imageView = _myImageView;
         
         // 添加到队列里面
         [_queue addOperation:op];
     }
     
     @end
    

八. Block

1. 没有返回值的block
- (void)testNoReturnBlock
{
    // 1. 没有参数的
    // 声明
    void (^block1)(void);
    // 赋值
    block1 = ^{
        NSLog(@"没有返回值没有参数");
    };
    // 使用
    block1();
    
    // 2. 有一个参数, 参数是基本类型
    // 声明
    void (^block2)(int a);
    // 赋值
    block2 = ^(int a){
        NSLog(@"a = %d", a);
    };
    // 使用
    block2(10);
    
    // 3. 有一个参数, 参数是对象类型
    // 声明
    void (^block3)(NSString *);
    // 赋值
    block3 = ^(NSString *str){
        NSLog(@"%@", str);
    };
    // 使用
    block3(@"张三");
    
    // 4. 两个参数, 一个基本类型, 一个对象类型
    // 声明
    void (^block4)(int age, NSString *name);
    // 赋值
    block4 = ^(int age, NSString *name){
        NSLog(@"%@'s %d years old", name, age);
    };
    // 使用
    block4(54, @"Lau");
}
2. 有返回值, 返回值类型是基本类型(int为例)
- (void)testIntValueReturn
{
    // 1. 没有参数
    // 声明
    int (^block1)(void);
    // 赋值
    block1 = ^{
        return 10;
    };
    // 使用
    NSLog(@"%d", block1());
    
    // 2. 有一个参数, 参数是基本类型
    // 声明
    int (^block2)(int);
    // 赋值
    block2 = ^(int a){
        return a;
    };
    // 使用
    NSLog(@"%d", block2(5));
    
    // 3. 有一个参数, 参数是对象类型
    // 声明
    int (^block3)(NSString *);
    block3 = ^(NSString *str){
        return (int)str.length;
    };
    NSLog(@"%d", block3(@"Sunshine"));
    
    // 4. 有两个参数, 都是对象类型
    // 声明
    int (^block4)(NSString *str1, NSString *str2);
    // 赋值
    block4 = ^(NSString *str1, NSString *str2){
        return (int)(str1.length + str2.length);
    };
    NSLog(@"%d", block4(@"Sunshine", @"Rain"));
}
3. 有返回值, 返回值类型是基本类型(int为例)
- (void)testNSStringValueReturn
{
    // 1. 没有参数
    // 声明
    NSString *(^block1)(void);
    // 赋值
    block1 = ^{
        return @"Nexus";
    };
    // 使用
    NSLog(@"%@",block1());
    
    // 2. 有一个参数, 参数是基本类型
    // 声明
    NSString *(^block2)(int);
    // 赋值
    block2 = ^(int a){
        return [NSString stringWithFormat:@"%d", a];
    };
    // 使用
    NSLog(@"%@", block2(5));
    
    // 3. 有一个参数, 参数是对象类型
    // 声明
    NSString *(^block3)(NSString *);
    block3 = ^(NSString *str){
        return str;
    };
    NSLog(@"%@", block3(@"Sunshine"));
    
    // 4. 有两个参数, 都是对象类型
    // 声明
    NSString *(^block4)(NSString *str1, NSString *str2);
    // 赋值
    block4 = ^(NSString *str1, NSString *str2){
        return [NSString stringWithFormat:@"%@ %@", str1, str2];
    };
    NSLog(@"%@", block4(@"Sunshine", @"Rain"));
}  
4. 测试
- (void)test
{
    // 1. 没有返回值, 有2个参数, 一个基本类型, 一个对象类型
    // 声明并赋值
    void (^block1)(int age, NSString *name) = ^(int age, NSString *name){
        NSLog(@"%@'s %d years old", name, age);
    };
    // 使用
    block1(23, @"Yuen");
    
    // 2. 返回值为int类型, 有2个参数, 参数是NSString类型
    // 声明并赋值
    int (^block2)(NSString *, NSString *) = ^(NSString *str1, NSString *str2){
        return (int)(str1.length + str2.length);
    };
    // 使用
    NSLog(@"%d", block2(@"Sunshine", @"Rain"));
    
    // 3. 返回值是NSString类型, 有2个参数, 参数是NSString类型
    NSString *(^block3)(NSString *,NSString *) = ^(NSString *str1, NSString *str2){
        return [NSString stringWithFormat:@"%@%@", str1, str2];
    };
    // 使用
    NSLog(@"%@", block3(@"Sunshine", @"Rain"));
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容