IOS_多线程_售票

H:/1007/01_多线程_大任务_MainViewController.m

//  MainViewController.m
//  多线程-01.大任务
//  Created by apple on 13-10-7.
#import "MainViewController.h"
@interface MainViewController ()
@property (weak, nonatomic) UIImageView *imageView;
@end
@implementation MainViewController
/*
 NSObject多线程方法

 1. [NSThread currentThread] 可以返回当前运行的线程
    num = 1 说明是主线程
    在任何多线程技术中(NSThread,NSOperation,GCD),
	均可以使用此方法,查看当前的线程情况。

 2. 新建后台线程,调度任务
    [self performSelectorInBackground:@selector(bigTask)
										withObject:nil]

    使用performSelectorInBackground是可以修改UI的,
	但是,强烈不建议如此使用。

 3. 更新界面
    使用performSelectorOnMainThread可以在主线程上执行任务。
	绝大多数最后一个参数是YES,即等待,直到它执行完
    提示:NSObject对象均可以调用此方法。

 4. 内存管理
    线程任务要包在@autoreleasepool(自动释放池)中,
				否则容易引起内存泄露,而且非常难发现。
 */
- (void)viewDidLoad
{
    [super viewDidLoad];
	// 创建第1个按钮,并为其添加点击事件
    UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [btn1 setFrame:CGRectMake(110, 100, 100, 40)];
    [btn1 setTitle:@"大任务" forState:UIControlStateNormal];
    [btn1 addTarget:self action:@selector(btnClick_1)
						  forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn1];
	// 创建第2个按钮,并为其添加点击事件
    UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [btn2 setFrame:CGRectMake(110, 200, 100, 40)];
    [btn2 setTitle:@"小任务" forState:UIControlStateNormal];
    [btn2 addTarget:self action:@selector(btnClick_2)
						  forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn2];
    NSLog(@"%@", [NSThread currentThread]);
	// 当前主线程中设置头像
    UIImageView *imageView = [[UIImageView alloc]initWithFrame:
										CGRectMake(110, 260, 100, 100)];
    UIImage *image = [UIImage imageNamed:@"头像1.png"];
    [imageView setImage:image];
    [self.view addSubview:imageView];
    self.imageView = imageView;
}
// 响应按钮1的点击事件
- (void)btnClick_1
{
    // 在后台调用耗时操作
    // performSelectorInBackground会新建一个后台线程,
	// 并在该线程中执行调用的方法
    [self performSelectorInBackground:@selector(btn_1_bigTask)
										withObject:nil];
    NSLog(@"大任务按钮: %@", [NSThread currentThread]);
}
#pragma mark 耗时操作
- (void)btn_1_bigTask
{
    @autoreleasepool {
        for (NSInteger i = 0; i < 300; i++) {
            NSString *str = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", str);
        }
        NSLog(@"大任务 - %@", [NSThread currentThread]);
        UIImage *image = [UIImage imageNamed:@"头像2.png"];
        // 在主线程中修改self.imageView的image
		// 调用self即当前控制器的方法,在主线程中设置头像
        [self performSelectorOnMainThread:@selector(changeImage:)
							  withObject:image waitUntilDone:YES];
		// 直接调用self.imageView自己的setImage:方法,并传递参数image
        [self.imageView performSelectorOnMainThread:@selector(setImage:)
									withObject:image waitUntilDone:YES];
    }
}
// 自定义方法,设置头像
- (void)changeImage:(UIImage *)image
{
    NSLog(@"修改头像 %@", [NSThread currentThread]);
    [self.imageView setImage:image];
}

// 响应按钮2的点击,调用自定义方法
- (void)btnClick_2
{
    NSLog(@"小任务按钮:%@", [NSThread currentThread]);
    [self btn_2_smallTask];
}
// 自定义方法
- (void)btn_2_smallTask
{
    NSString *str = nil;
    for (NSInteger i = 0; i < 30000; i++) {
        str = [NSString stringWithFormat:@"i = %i", i];
    }
    NSLog(@"%@", str);
    NSLog(@"小任务 - %@", [NSThread currentThread]);
}
@end

H:/1007/02_多线程_加载图片_MainViewController.m

//  MainViewController.m
//  多线程-02.加载图片
//  Created by apple on 13-10-7.
#import "MainViewController.h"
@interface MainViewController ()
// 图像集合
@property (strong, nonatomic) NSSet *imageViewSet;
// 定义操作队列,NSOperationQueue
@property (strong, nonatomic) NSOperationQueue *operationQueue;
@end
@implementation MainViewController
/*
 1. NSThread
    1> 类方法 detachNewThreadSelector
        直接启动线程,调用选择器方法

    2> 成员方法 initWithTarget
        需要使用start方法,才能启动实例化出来的线程

    优点:简单
    缺点:
        * 控制线程的生命周期比较困难
        * 控制并发线程数
		* 存在死锁隐患
        * 先后顺序困难
        例如:下载图片(后台线程) -> 滤镜美化(后台线程) -> 更新UI(主线程)

 2. NSOperation
    1> NSInvocationOperation
    2> NSBlockOperation

    定义完Operation之后,将操作添加到NSOperationQueue即可启动线程,执行任务

    使用:
    1>  setMaxConcurrentOperationCount 可以控制同时并发的线程数量
    2>  addDependency 可以指定线程之间的依赖关系,
					  从而达到控制线程执行顺序的目的,如先下载,再渲染图片

    提示:
    要更新UI,需要使用[NSOperationQueue mainQueue]addOperationWithBlock:
    在主操作队列中更新界面

 3. GCD
     1) 全局global队列,如果是同步,则不开线程,在主队列中执行
     方法:dispatch_get_global_queue(获取全局队列)
     优先级:DISPATCH_QUEUE_PRIORITY_DEFAULT
     所有任务是并发(异步)执行的

     2) 串行队列  必须开一条新线程,全是顺序执行的
     方法:dispatch_queue_create(创建串行队列,串行队列不能够获取)
     提示:队列名称可以随意,不过不要使用@

     3) 主队列
     主线程队列
     方法:dispatch_get_main_queue(获取主队列)

    在gcd中,同步还是异步取决于任务执行所在的队列,更方法名没有关系

    具体同步、异步与三个队列之间的关系,一定要反复测试,体会!

  4.全局队列(可能会开启多条线程,如果是同步方法,则不开线程,只在主线程里)
		dispatch_queue_t queue = dispatch_get_global_queue(
						DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
		串行队列(只可能会开启一条线程)
		dispatch_queue_t queue = dispatch_queue_create("myQueue",
										DISPATCH_QUEUE_SERIAL);
		主队列
		dispatch_get_main_queue();

	异步操作
		dispatch_async 异步方法无法确定任务的执行顺序

	同步操作
		dispatch_sync 同步方法会依次执行,能够决定任务的执行顺序
		同步操作与队列无关,所有的队列的同步,都是顺序执行的
		更新界面UI时,最好使用同步方法

	GCD的优点:
		充分利用多核
		所有的多线程代码集中在一起,便于维护
		GCD中无需使用@autoreleasepool

		如果要顺序执行,可以使用dispatch_sync同步方法
		dispatch_async无法确定任务的执行顺序
		调用主线程队列任务更新UI时,最好使用同步方法
	单例:
		保证在内存中永远只有类的单个实例

		建立方法:
		1,声明一个静态成员变量,记录唯一实例
		2,重写allocWithZone方法
			allocWithZone方法是对象分配内存空间时,最终会调用的方法,
			重写该方法,保证只会分配一个内存空间
		3,建立sharedXXX类方法,便于其他类访问
 */
- (void)viewDidLoad
{
    [super viewDidLoad];
	// 自定义方法,设置UI界面
    [self setupUI];
    // 实例化操作队列,NSOperationQueue
    self.operationQueue = [[NSOperationQueue alloc]init];
}
// 自定义方法,设置UI界面
- (void)setupUI
{
    // 实例化图像视图集合
    NSMutableSet *imageSet = [NSMutableSet setWithCapacity:28];
    // 虽然只有17张图片,但是每行显示4张,一共显示7行(重复使用)
	// 每张小图片宽 80,高 50
    NSInteger w = 80;
    NSInteger h = 50;
	// 弄出7行 X 4列图片
    for (NSInteger row = 0; row < 7; row++) {
        for (NSInteger col = 0; col < 4; col++) {
            // 计算图片的位置
			// 第几列 即所在的列数 决定了x坐标
			// 第几行 即所在的行数 决定了y坐标
            NSInteger x = col * w;
            NSInteger y = row * h;
            UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(x, y, w, h)];
            /* 下面是不使用多线程,设置图片
            NSInteger index = (row * 4 + col) % 17 + 1;
            NSString *imageName = [NSString stringWithFormat:@"NatGeo%02d.png", index];
            UIImage *image = [UIImage imageNamed:imageName];
            [imageView setImage:image];
			*/
			// 因为要使用多线程设置图片,所以这只添加imageView,并不设置图片
            [self.view addSubview:imageView];
            [imageSet addObject:imageView];
        }
    }
	// 成员变量 NSSet,记住所有的imageSet,方便多线程方法中为其设置图片
    self.imageViewSet = imageSet;
    // 添加按钮
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [btn setFrame:CGRectMake(110, 385, 100, 40)];
    [btn setTitle:@"设置图片" forState:UIControlStateNormal];
    [btn addTarget:self action:@selector(click)
							forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn];
}
#pragma mark 按钮的监听方法,设置图片
- (void)click
{
	// 调用GCD 设置图片
    [self gcdLoad];
}
// 多线程之NSThread,类方法detach线程,或者alloc创建线程,并手动start线程
- (void)threadLoad
{
	// 遍历成员变量NSSet 为每一个imageView设置图片
    for (UIImageView *imageView in self.imageViewSet) {
        // 方式1,类方法,detach分离,新建28条线程,并自动调用threadLoadImage方法
        [NSThread detachNewThreadSelector:@selector(threadLoadImage:)
									toTarget:self withObject:imageView];
        // 方式2,alloc创建线程,并需要手动开启线程,才会执行threadLoadImage方法
        NSThread *thread = [[NSThread alloc]initWithTarget:self
					selector:@selector(threadLoadImage:) object:imageView];
		// alloc出来的线程,必须手动start才有效
        [thread start];
    }
}
// NSThread线程的任务:加载图片方法
- (void)threadLoadImage:(UIImageView *)imageView
{
	// 设置imageView的图片
    // 线程方法一定要加autoreleasepool
    @autoreleasepool {
        // 28条线程,都在一开始睡眠1秒
        [NSThread sleepForTimeInterval:1.0f];
        NSInteger index = arc4random_uniform(17) + 1;
        NSString *imageName = [NSString stringWithFormat:@"NatGeo%02d.png",
															index];
        UIImage *image = [UIImage imageNamed:imageName];
        // imageView直接在主线程上执行setImage方法,更新UI,参数就是image
        [imageView performSelectorOnMainThread:@selector(setImage:)
										withObject:image waitUntilDone:YES];
    }
}
#pragma mark NSOperation方法
// 多线程之2,NSXxxOperation依赖关系演示
- (void)operationDemo
{
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下载 %@", [NSThread currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"美化 %@", [NSThread currentThread]);
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"更新 %@", [NSThread currentThread]);
    }];
    // Dependency依赖,1完成,再执行2,最后执行3
    // 提示:依赖关系可以多重依赖
    // 注意:不要建立循环依赖,嵌套依赖
    [op2 addDependency:op1];
    [op3 addDependency:op2];
	// 将NSOperation添加到成员变量NSOperationQueue,操作队列中
    [self.operationQueue addOperation:op3];
    [self.operationQueue addOperation:op1];
    [self.operationQueue addOperation:op2];
}
// 多线程之2,NSBlockOperation
- (void)operationBlockLoad
{
	// 遍历成员变量NSSet,为每一个imageView设置图片
    for (UIImageView *imageView in self.imageViewSet) {
		// 创建一个操作 NSOperation
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
			// 执行 当前控制器的 自定义方法,,operationLoadImage
            [self operationLoadImage:imageView];
        }];
        // 必须将操作添加到操作队列之中,才有效,注:会自动启动~
        [self.operationQueue addOperation:op];
    }
}
// 多线程之2,NSInvocationOperation
-(void)operationLoad
{
    // NSOperationQueue优点:可以设置同时并发执行的线程的数量
	// 即使开了20条线程,同一时刻也只会执行其中的4条线程
    [self.operationQueue setMaxConcurrentOperationCount:4];
	// 遍历成员变量NSSet,为每一个imageView设置图片
    for (UIImageView *imageView in self.imageViewSet) {
		// 创建一个操作 NSOperation
        NSInvocationOperation *op = [[NSInvocationOperation alloc]
				initWithTarget:self selector:@selector(operationLoadImage:)
				object:imageView];
        // 如果直接调用operation的start方法,是在主线程队列上运行的,不会开启新的线程
        [op start];
        // 必须将操作Operation添加到操作队列,才会开启新的线程执行任务,注:自动开启
        [self.operationQueue addOperation:op];
    }
}
// 多线程之2,NSXxxOperation任务的具体代码,加载图片方法
- (void)operationLoadImage:(UIImageView *)imageView
{
    // 线程方法一定要加autoreleasepool
    @autoreleasepool {
        // 设置imageView的内容
		// 模拟网络延时
        [NSThread sleepForTimeInterval:1.0f];
        NSInteger index = arc4random_uniform(17) + 1;
        NSString *imageName = [NSString stringWithFormat:@"NatGeo%02d.png",
															index];
        UIImage *image = [UIImage imageNamed:imageName];
        // 必须在主线程队列上更新UI,必须使用NSOperationQueue mainQueue
        [[NSOperationQueue mainQueue]addOperationWithBlock:^{
            [imageView setImage:image];
        }];
    }
}
// 多线程之3,GCD,抽象程度最高,用户主要精力只要放在业务上即可
- (void)gcdDemo
{
    /*
     1. 全局global队列
     方法:dispatch_get_global_queue(获取全局队列)
     优先级:DISPATCH_QUEUE_PRIORITY_DEFAULT
     所有任务是并发(异步)执行的,会创建N条线程

     2. 串行队列
     方法:dispatch_queue_create(创建串行队列,串行队列不能够获取)
     提示:队列名称可以随意,不过不要使用@
	 任务是同步,只会创建一个线程

     3. 主队列
     主线程队列
     方法:dispatch_get_main_queue(获取主队列)

     在gcd中,同步还是异步取决于任务执行所在的队列,更方法名没有关系
		如果是全局队列就是异步,会创建多条线程
		如果是自创建的队列,则是串行,同步的,且只会创建一条线程

	 派发dispatch_
     异步async不执行,并发执行
     优先级priority,使用默认优先级即可
     1. 在全局队列中调用异步任务
     1) 全局队列,全局调度队列是有系统负责的,开发时不用考虑并发线程数量问题
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     串行队列,需要创建,不能够get
     DISPATCH_QUEUE_SERIAL串行队列

    dispatch_queue_t queue = dispatch_queue_create("myQueue",
									  DISPATCH_QUEUE_SERIAL);
	GCD是基于C语言的框架
	工作原理:
	让程序平行排队的特定任务,根据可用的处理资源,安排它们在任何可用的处理器上执行任务
	要执行的任务可以是一个函数或者一个block
	底层是通过线程实现的,不过程序员可以不必关注实现的细节
	GCD中的FIFO队列称为dispatch queue,可以保证先进来的任务先得到执行
	dispatch_notify 可以实现监听一组任务是否完成,完成后得到通知
	GCD队列:
	全局队列:所有添加到全局队列中的任务都是并发执行的
	串行队列:所有添加到串行队列中的任务都是顺序执行的
	主队列:所有添加到主队列中的任务都是在主线程中执行的
*/
    // 主队列,即在主线程上运行
    dispatch_queue_t queue = dispatch_get_main_queue();
    // 2) 在主队列中没有异步和同步,所有都是在主线程中完成的
    dispatch_async(queue, ^{
        NSLog(@"任务1 %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任务2 %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任务3 %@", [NSThread currentThread]);
    });
}
// 多线程之3, GCD加载图像,具体的核心业务,为NSSet中每个imageView设置图片
- (void)gcdLoad
{
    // 1) 获取全局队列,可以执行异步
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
	// 遍历成员变量NSSet,为每一个imageView设置图片
    for (UIImageView *imageView in self.imageViewSet) {
        // 2) 在全局队列上执行异步方法,开启N条线程,加载并设置图像
        dispatch_async(queue, ^{
            NSLog(@"GCD- %@", [NSThread currentThread]);
            NSInteger index = arc4random_uniform(17) + 1;
            NSString *imageName = [NSString stringWithFormat:@"NatGeo%02d.png",
																index];
            // 通常此异步方法里面获取的image是网络上的
            UIImage *image = [UIImage imageNamed:imageName];
            // 3) 最后必须在主线程队列中设置图片,异步
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"更新图片- %@", [NSThread currentThread]);
                [imageView setImage:image];
            });
        });
    }
}
@end

H:/1007/03_多线程_卖票_MainViewController.m

//  MainViewController.m
//  多线程-03.卖票
//  Created by apple on 13-10-7.
/*
系统预设
	共有30张票可以销售(开发时可以少一些,专注实现)
	售票工作由两个线程并发进行
	没有可出售票据时,线程工作停止
	两个线程的执行时间不同,模拟售票人员效率不同
	使用一个多行文本框公告售票进度(主线程更新UI)
线程工作安排
	主线程:负责更新UI
	线程1:模拟第1名卖票员
	线程2:模拟第2名卖票员
	两个线程几乎同时开始卖票
*/
#import "MainViewController.h"
#import "Ticket.h"
@interface MainViewController ()
@property (weak, nonatomic) UITextView *textView;
@property (strong, nonatomic) NSOperationQueue *operationQueue;
@end
@implementation MainViewController
- (void)viewDidLoad
{
    [super viewDidLoad];
    // 建立多行文本框
    UITextView *textView = [[UITextView alloc]initWithFrame:self.view.bounds];
    // 禁止编辑
    [textView setEditable:NO];
    [self.view addSubview:textView];
    self.textView = textView;
    // 预设可以卖30张票
    [Ticket sharedTicket].tickets = 30;
    // 实例化操作队列,NSOperationQueue
    self.operationQueue = [[NSOperationQueue alloc]init];
    // 调用自定义方法,开始卖票
    [self operationSales];
}
// 多线程卖票之一:NSOperation
- (void)operationSales
{
    // 提示,operation中没有群组任务完成通知功能
    // 设置操作队列,最大同时并发线程数:两个线程卖票
    [self.operationQueue setMaxConcurrentOperationCount:2];
    [self.operationQueue addOperationWithBlock:^{
        [self operationSaleTicketWithName:@"op-1"];
    }];
    [self.operationQueue addOperationWithBlock:^{
        [self operationSaleTicketWithName:@"op-2"];
    }];
    [self.operationQueue addOperationWithBlock:^{
        [self operationSaleTicketWithName:@"op-3"];
    }];
}
// 多线程卖票之一:NSOperation 核心卖票代码
- (void)operationSaleTicketWithName:(NSString *)name
{
    while (YES) {
        // 同步锁synchronized要锁的范围,对被抢夺资源修改/读取的代码部分
        @synchronized(self) {
            // 判断是否还有票
            if ([Ticket sharedTicket].tickets > 0) {
                [Ticket sharedTicket].tickets--;
                // 提示,涉及到被抢夺资源的内容定义方面的操作,千万不要跨线程去处理
                NSString *str = [NSString stringWithFormat:
									@"剩余票数 %d 线程名称 %@",
									[Ticket sharedTicket].tickets, name];

                // 在mainQueue主线程中更新UI
                [[NSOperationQueue mainQueue]addOperationWithBlock:^{
					// 调用自定义方法,更新编辑框的内容,并滚动至最后一行
                    [self appendContent:str];
                }];
            } else {
                NSLog(@"卖票完成 %@ %@", name, [NSThread currentThread]);
                break;
            }
        }
        // 模拟卖票休息,不同的窗口,工作效率不同
        if ([name isEqualToString:@"op-1"]) {
            [NSThread sleepForTimeInterval:0.6f];
        } else {
            [NSThread sleepForTimeInterval:0.4f];
        }
    }
}
#pragma mark 更新UI,追加当前余票数,到多行文本框
- (void)appendContent:(NSString *)text
{
    // 1. 取出多行文本框里面原来的内容
    NSMutableString *str = [NSMutableString
							stringWithString:self.textView.text];
    // 2. 将text追加至textView内容的末尾
    [str appendFormat:@"%@\n", text];
    // 3. 使用追加后的文本,替换textView中的内容
    [self.textView setText:str];
    // 4. 将textView滚动至视图底部,保证能够及时看到新追加的内容
	// 参数1是index,参数2是截取的长度
    NSRange range = NSMakeRange(str.length - 1, 1);
	// 5.滚动到编辑框的最后一行
    [self.textView scrollRangeToVisible:range];
}
// 多线程卖票之二:NSThread
- (void)threadSales
{
	// 类方法创建新线程,并且直接运行
    [NSThread detachNewThreadSelector:@selector(threadSaleTicketWithName:)
									toTarget:self withObject:@"thread-1"];
	// 类方法创建新线程,并且直接运行
    [NSThread detachNewThreadSelector:@selector(threadSaleTicketWithName:)
									toTarget:self withObject:@"thread-2"];
}
// 多线程卖票之二:NSThread 核心卖票代码
- (void)threadSaleTicketWithName:(NSString *)name
{
    // 使用NSThread时,线程调用的方法千万要使用@autoreleasepool
    @autoreleasepool {
        while (YES) {
			// 同步锁synchronized要锁的范围,对被抢夺资源修改/读取的代码部分
            @synchronized(self) {
                if ([Ticket sharedTicket].tickets > 0) {
                    [Ticket sharedTicket].tickets--;
                    NSString *str = [NSString stringWithFormat:
									@"剩余票数 %d 线程名称 %@",
									[Ticket sharedTicket].tickets, name];
                    // 在MainThread主线程中更新UI
					// 调用自定义方法,更新编辑框的内容,并滚动至最后一行
                    [self performSelectorOnMainThread:
								@selector(appendContent:) withObject:str
								waitUntilDone:YES];
                } else {
                    break;
                }
            }
            // 模拟卖票休息,不同的窗口,工作效率不同
            if ([name isEqualToString:@"thread-1"]) {
                [NSThread sleepForTimeInterval:1.0f];
            } else {
                [NSThread sleepForTimeInterval:0.1f];
            }
        }
    }
}
// 多线程卖票之三:GCD,单纯只创建三个异步任务分别卖票
- (void)gcdSales_without_group
{
    // 1) 创建全局队列
    dispatch_queue_t queue = dispatch_get_global_queue(
					DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 2) 创建三个异步任务分别卖票
    dispatch_async(queue, ^{
        [self gcdSaleTicketWithName:@"gcd-1"];
    });
    dispatch_async(queue, ^{
        [self gcdSaleTicketWithName:@"gcd-2"];
    });
    dispatch_async(queue, ^{
        [self gcdSaleTicketWithName:@"gcd-3"];
    });
}
// 多线程卖票之三:GCD,创建组,将三个异步任务添加到组
- (void)gcdSales_with_group
{
    // 1) 创建全局队列
    dispatch_queue_t queue = dispatch_get_global_queue(
					DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 2. GCD中可以将一组相关联的操作,定义到一个群组中
    // 定义到群组中之后,当所有线程完成时,可以获得通知
    // 3. 定义群组
    dispatch_group_t group = dispatch_group_create();
    // 4. 定义群组的异步任务
    dispatch_group_async(group, queue, ^{
        [self gcdSaleTicketWithName:@"gcd-1"];
    });
    dispatch_group_async(group, queue, ^{
        [self gcdSaleTicketWithName:@"gcd-2"];
    });
    // 3) 群组任务完成通知,当前组中的三个线程全部完成的时候,会调用~
    dispatch_group_notify(group, queue, ^{
        NSLog(@"卖完了");
    });
}
// 多线程卖票之三:GCD 核心卖票代码
- (void)gcdSaleTicketWithName:(NSString *)name
{
    while (YES) {
        // 同步锁synchronized要锁的范围,对被抢夺资源修改/读取的代码部分
        @synchronized(self) {
            if ([Ticket sharedTicket].tickets > 0) {
                [Ticket sharedTicket].tickets--;
                // 提示内容
                NSString *str = [NSString stringWithFormat:@"剩余票数 %d,
						线程名称 %@", [Ticket sharedTicket].tickets, name];
                // 在dispatch_get_main_queue主线程中更新UI
                dispatch_sync(dispatch_get_main_queue(), ^{
					// 调用自定义方法,更新编辑框的内容,并滚动至最后一行
                    [self appendContent:str];
                });
            } else {
                break;
            }
        }
        // 模拟卖票休息,不同的窗口,工作效率不同
        if ([name isEqualToString:@"gcd-1"]) {
            [NSThread sleepForTimeInterval:1.0f];
        } else {
            [NSThread sleepForTimeInterval:0.2f];
        }
    }
}
@end

H:/1007/03_多线程_卖票_单例_Ticket.h

//  Ticket.h
//  多线程-03.卖票
//  Created by apple on 13-10-7.
//  Copyright (c) 2013年 itcast. All rights reserved.

#import <Foundation/Foundation.h>

@interface Ticket : NSObject

// 实例化票据的单例
+ (Ticket *)sharedTicket;

// 在多线程应用中,所有被抢夺资源的属性需要设置为原子属性
// 系统会在多线程抢夺时,保证该属性有且仅有一个线程能够访问
// 注意:使用atomic属性,会降低系统性能,在开发多线程应用时,尽量不要资源
// 另外,atomic属性,必须与@synchronized(同步锁)一起使用

// 票数 atomic
@property (assign, atomic) NSInteger tickets;

@end

H:/1007/03_多线程_卖票_单例_Ticket.m

//  Ticket.m
//  多线程-03.卖票
//  Created by apple on 13-10-7.
//  Copyright (c) 2013年 itcast. All rights reserved.
#import "Ticket.h"
static Ticket *SharedInstance;
@implementation Ticket
/**
 实现单例模型需要做三件事情
 1. 使用全局静态变量记录住第一个被实例化的对象
    static Ticket *SharedInstance
 2. 重写allocWithZone方法,并使用dispatch_once_t,从而保证在多线程情况下,
    同样只能实例化一个对象副本
 3. 建立一个以shared开头的类方法实例化单例对象,便于其他类调用,同时不容易引起歧义
    同样用dispatch_once_t确保只有一个副本被建立

 关于被抢夺资源使用的注意事项

 在多线程应用中,所有被抢夺资源的属性需要设置为原子属性
 系统会在多线程抢夺时,保证该属性有且仅有一个线程能够访问

 注意:使用atomic属性,会降低系统性能,在开发多线程应用时,尽量不要抢资源
 另外,atomic属性,必须与@synchronized(同步锁)一起使用
 */
// 使用内存地址实例化对象,所有实例化方法,最终都会调用此方法
// 要实例化出来唯一的对象,需要一个变量记录住第一个实例化出来的对象
+ (id)allocWithZone:(NSZone *)zone
{
    // 解决多线程中,同样只能实例化出一个对象副本
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SharedInstance = [super allocWithZone:zone];
    });
    return SharedInstance;
}
// 建立一个单例对象,便于其他类调用
+ (Ticket *)sharedTicket
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SharedInstance = [[Ticket alloc]init];
    });
    return SharedInstance;
}
@end

IOS_多线程_售票,布布扣,bubuko.com

时间: 2024-10-15 21:46:29

IOS_多线程_售票的相关文章

IOS_多线程_ASI_AFN_UIWebView

H:/0730/00_多线程4种售票_ViewController.h // // ViewController.h // 卖票 // // Created by apple on 13-7-29. // Copyright (c) 2013年 itcast. All rights reserved. // #import <UIKit/UIKit.h> @interface ViewController : UIViewController // 多行文本提示框 @property (wea

多线程_创建线程_继承Thread类

public class ThreadDemo {   public static void main(String[] args){         Demo d = new Demo();   d.start();      for(int i = 0;i < 100;i++){      System.out.println("MainThread" + i);   }   } } class Demo extends Thread {   public void run(

iOS_多线程(一)

在学习多线程之前首先搞清楚以下几个问题. 并发:在同一时刻,只有一条指令被执行,多条指令进行快速切换执行. 并行:在同一时刻,多个处理器可以处理多条指令 1.什么是进程? 一个运行的程序就是一个进程或一个任务 多个进程之间是具有相互独立的内存空间. 2.什么是线程? 进程当中代码的一个执行路径. 多线程:也就是说代码有多个执行路径. 3.进程与线程的关系? 一个进程至少包含一个线程(主线程),线程是程序的执行流 主线程是其他线程的父线程,所有的界面的显示操作必须在主线程进行. 一个进程中包含多个

IOS_地图_定位_天气预报_Block回调_单例

H:/1021/00_block回调.h /* 通过block回调 定义block代码块,目的是解析完成之后调用 返回值是 void 参数是 数组,里面的每个成员是一个NSString*/ typedef void(^WeatherFinishedBlock)(NSArray *dataList); @interface WeatherXMLPaser : NSObject // 解析器解析数据,参数1是要解析的数据,参数2是解析完毕回调的代码块 - (void)parserWeatherDat

多线程简易售票程序--孙鑫视频示例

孙鑫视频多线程的第一个例子----简易的售票程序 #include <Windows.h> #include <iostream> int ticket = 100; //定义互斥对象 /* 互斥对象属于内核对象,它能够准确保证线程拥有对单个资源的互斥访问 互斥对象包含一个使用数量 线程ID 计数器 ID用于标识系统中哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数 */ HANDLE hMutex; DWORD WINAPI FunProc_1(LPVOID lp

使用无锁完成多线程模拟售票, 理解无锁是啥?

实现的模拟多线程实现售票是每个学习多线程的初学者必须要学会掌握的知识点, 既然掌握的它, 我们自然要举一反三 So~, 无锁版出现了 What无锁? 假如两个线程同时修改一个变量的场景下 我们需要三个值, 预期值(线程副本变量中的值), 主存值(从主存变量中的值), 新值(我们要设置的值) 如果 预期值 不等于 主存值 则忽略 新值 写入  =========> 这句话是一个原子操作, 是不可分割的(就是内存屏障), 在执行这个过程中, 是不会失去时间片的 如果 预期值 等于 主存值 则  新值

python多线程实现售票

我们使用mutex(Python中的Lock类对象)来实现线程的同步: lock.acquire() 相当于P操作,得到一个锁,锁定lock.release()相当于V操作,释放一个锁,释放 1 # -*- coding: cp936 -*- 2 import threading # Python主要通过标准库中的threading包来实现多线程 3 import time 4 import os 5 6 7 def doChore(): #作为间隔 每次调用间隔0.5s 8 time.slee

Java多线程_复习

java多线程的常见例子 一.相关知识: Java多线程程序设计到的知识: (一)对同一个数量进行操作 (二)对同一个对象进行操作 (三)回调方法使用 (四)线程同步,死锁问题 (五)线程通信 等等 二.示例一:三个售票窗口同时出售20张票; 程序分析:1.票数要使用同一个静态值 2.为保证不会出现卖出同一个票数,要java多线程同步锁. 设计思路:1.创建一个站台类Station,继承Thread,重写run方法,在run方法里面执行售票操作!售票要使用同步锁:即有一个站台卖这张票时,其他站台

【Java】多线程_学习笔记

多线程 1.进程 进程:当一个程序进入内存运行时,它就成为了进程.进程具有独立性.动态性.并发性. A.独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间.在没有进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间. B.动态性:进程与程序的区别在于,程序是一个静态的指令集合,而进程是一个正在运行的指令集合.进程有时间的概念,有自己的生命周期和各种不同的状态,而程序不具备这种概念. C.并发性:多个进程可以在单个处理器上并发执行,相互之