代码块与并发性

1.代码块

代码块对象(通常称为代码块)是对C语言中函数的扩展。除了函数中的代码,代码块还包含变量绑定。代码块有时也称为闭包(closure)。

代码块包含两种类型的绑定:自动型和托管型。自动绑定(automatic binding)使用的是栈中的内存,而托管绑定(managed binding)是通过堆创建的。

1.1 代码块定义和实现

代码块借鉴了函数指针的语法。与函数指针相似,代码块具有以下特征:

  • 返回类型可以手动声明,也可以由编译器推导;
  • 具有指定类型的参数列表
  • 拥有名称

代码:

int (^square_block)( int number ) = ^(int number) {
     return (number * number);
};
int result = square_block(6);
NSLog(“Result = %d “,result);

说明:

等号前面的内容:int (^square_block)( int number ),是代码块的定义。

等号后面的内容:是代码块的实现内容。

一般我们可以用如下关系来表示它们:

returntype ( ^ blockname) ( list of arguments ) = ^( arguments ) {  body; };

1.2 使用代码块

可以像函数一样使用代码块。例如:

int result = square_block(6);

说明:代码块在使用的时候不需要^(幂符号),只有在定义的时候才需要。

使用代码块的时候通常不需要创建一个代码块变量,而是在代码中内联代码块的内容。通常需要将代码块作为参数的方法或函数。例如:

NSArray *array = [NSArray arrayWithObjects:@“a”,@“b”,@“c”,@“d”,nil];
NSArray *sortedArray = [array sortedArrayUsingComparator:^(NSString *object1, NSString *object2){
     return [object1 compare: object2];
}]; 

1.3 使用typedef关键字

像上面那样那么长的变量定义语句,在输入这些代码的时候很容易引起错误。我们可以用typedef关键字。

typedef double (^ MyBlockName)(double a, double b);

这行代码定义了一个名为MyBlockName的代码块变量类型,它包含两个双浮点类型的参数,并且返回一个双浮点类型的数值。

有了typedef,就可以像下面这样使用这个代码块变量:

MyBlockName *myBlock = ^(double a, double b){
     return a * b;
};
NSLog(@“%f, %f”, myBlock (2, 4 ) ,  myBlock (3, 4) );

1.4 代码块和变量

1.4.1 本地变量

本地变量就是和代码块在同一范围内声明的变量。

代码示例:

typedef double (^ MyBlock)(void);
double a = 10, b = 20;
MyBlock myBlock = ^(void){
     return a * b;
};
a = 30;
b = 20;
NSLog(@“%f”,myBlock());

这段代码最后输出地的是100,而不是600.因为变量是本地变量,代码块会在定义的时候复制并保存它们的状态。

1.4.2 参数变量

代码块中的参数变量和函数中的参数变量具有同样的作用。

typedef double (^ MyBlock)(double c, double d);
MyBlock myBlock = ^(double a, double b){
     return a * b;
};
NSLog:(@“%f, %f”,myBlock(12,2), myBlock(2,4));

1.4.3 __block 变量

本地变量会被代码块作为常量获取到。如果你想要修改他们的值,必须将他们声明为可修改的,否则像下面这个实例,编译时会出现错误:

double c = 3;
MyBlock myBlock = ^(double a, double b){
     c = a * b;
};

编译器会报这个错误:

Variable is not assignable (missing __block type specifier)

想要修复这个编译错误,需要将变量c标记为__block。

__block double c = 3;
MyBlock myBlock = ^(double a, double b){
     c = a * b;
};

有些变量是无法声明为__block类型的。

包括:

1)长度可变的数组

2)包含可变长度数组的结构体

1.4.4 代码块内部的本地变量

这些变量与本地变量具有相同的作用:

void (^MyBlock)(void) = ^(void){
          double a = 3;
          double b = 4;
          NSLog(@“%f”, a * b);
};
MyBlcok();

1.5 代码块与内存管理

在代码块中使用Objective-C变量时必须小心 ,以下规则能帮助你处理内存管理。

1)如果引用了一个Objective-C对象,必须要保留它;

2)如果通过引用访问了一个实例变量,要保留一次self(即执行方法的对象);

3)如果通过数值访问了一个实例变量,变量需要保留。

解释规则(1)的示例:

NSString *string1 = ^{
          return [_theString stringByAppendingString:_theString];
};

在这个示例中,_theString是声明了代码块的类中的实例变量。因为在代码块中直接访问了实例变量,所以包含它的对象(self)需要保留。

__block NSString *localObject = _theString;
NSString *string2 = ^{
          return [localObject stringByAppendingString:localObject];
};

在这个例子中,我们是间接访问:创建了一个指向实例变量的本地引用,并在代码块中使用。因此要保留的是localObject,而不是self。

因为代码块是对象,所以可以向它发送任何与内存管理有关的消息。在C语言级别中,必须使用Block_copy()和Block_release()函数来适当地管理内存.

MyBlock block1 = ^{
          NSLog(@"Block1”);
};
block1();

MyBlock block2 = ^{
          NSLog(@“Block2”);
};
block2();
Block_release(block2);

block2 = Block_copy(block1);
block2(); 

2.并发性

2.1 引入线程的概念

用来运行Xcode的Mac电脑的处理器至少拥有两个核心,也可能更多。现在最新的iOS设备都是多核的。这意味着你可以在同一时间进行多项任务。苹果公司提供了多种可以利用多核特性的API。能够在同一时间执行多项任务的程序称其为并发的(concurrent)程序。

利用并发性最基础的方法是使用POSIX线程来处理程序的不同部分使其能够独立执行。POSIX线程拥有支持C语言和Objective-C的API。编写并发程序需要创建多个线程,而编写线程代码是很具有挑战性的。

线程是级别较低的API,需要手动管理,处理所有的线程是需要技巧的,一旦遇到问题,可能不使用线程会更好一些。

百度百科线程:http://baike.baidu.com/view/1053.htm

2.2 GCD技术

苹果公司为了减轻在多核上变成的负担,引入了Grand Central Dispatch,我们称之为GCD。

  • GCD技术减少了不少线程管理的麻烦,如果要使用GCD,你需要提交代码块或者函数作为线程来运行。
  • GCD是一个系统级别(system-level)的技术,因此你可以在任意级别的代码中使用它。
  • GCD决定需要多少线程来安排他们运行的进度。
  • 因为GCD是运行在系统级别的,所以可以平衡应用程序所有内容的加载,这样可以提高计算机或设备的执行效率。

2.2.1 同步

我们如何在由多核组成的通路中管理交通呢?可以使用同步装置,比如在通道入口立一个标记(flag)或者一个互斥(mutex)。

说明:mutex是mutual exclusion 的缩写,它指的是确保两个线程不会在同一时间进入临界区。

Objective-C提供了一个语言级别的(language-level)关键字@synchronized。这个关键字拥有一个参数,通常这个对象是可以修改的。

@synchronized(theObject)
{
          //Critical section
}

它可以确保不同的线程会连续地访问临界区的代码。

如果你定义了一个属性,并且没有指定关键字nonatomic作为属性的特性,编译器会生成强制彼此互斥的getter和setter方法,但是这样设置代码和变量,会产生一些消耗,比直接访问慢一些。为了提高性能,可以添加nonatomic特性。

1.选择性能

NSObject提供方法以供一些代码只在后台执行。这些方法中都有performSelector:,最简单的就是performSelectorInBackground:WithObject:,它能在后台执行一个方法。它通过创建一个线程来运行方法。定义这些方法时必须遵从以下限制:

1)这些方法运行在各自的线程里,因此你必须为这些Cocoa对象创建一个自动释放池,而主自动释放池是与主线程相关的。

2)这些方法不能有返回值,并且要么没有参数,要么只有一个参数对象。换句话说,你只能使用以下代码格式中的一种:

-(void)myMethod;

-(void)myMethod:(id)myObject;

示例:

-(void)myBackgroundMethod
{
          @autoreleasepool
          {
                    NSLog(@“My Background Method”);
          }
}

或:

-(void)myBackgroundMethod:(id)myObject
{
          @autoreleasepool
          {
                    NSLog(@“My Background Method %@”,myObject);
          }
}

在后台执行你的方法:

[self performSelectorInBackground:@selector(myBackgroundMethod) withObject:nil];

或者:

[self performSelectorInBackground:@selector(myBackgroundMethod) withObject:argumentObjectl];

当方法执行结束之后,Objective-C运行时会特地清理并弃掉线程。需要注意:方法执行结束后并不会通知你,这是比较简单的代码。如果想要做一些更复杂的事情,需要学习调度队列。

2 调度队列

GCD可以使用调度队列(dispatch queue),只需写下你的代码,把它指派为一个队列(百度百科“队列”:http://baike.baidu.com/subview/38959/14411740.htm),系统就会执行它了。可以同步或异步执行任意代码。

有三种类型的队列:

1)连续队列:每个连续队列都会根据指派的顺序执行任务。可以按自己的想法创建任意数量的队列,他们会并行操作任务。

2)并发队列:每个并发队列都能并发执行一个或多个任务。任务会根据指派到队列的顺序开始执行。你无法创建连续队列,只能从系统提供的三个队列内选择一个来使用。

3)主队列:它是应用程序中有效的主队列,执行的是应用程序的主线程任务。

死锁(deadlock):指的是两个或多个任务在等待其他任务执行结束,就像是几辆汽车同时位于一个很拥挤的停车场里。

下面讨论三种队列及其使用:

  • 连续队列

当有一连串任务需要按照一定顺序执行的时候,可以使用连续队列。任务执行顺序为先进先出(FIFO):只要任务是异步提交的,队列会确保任务根据预定顺序执行。这些队列都是不会发生死锁的。

使用:

dispatch_queue_t my_serial_queue;

my_serial_queue = dispatch_queue_create(“com.appress.MySerialQueue1”,NULL);

第一个参数是队列的名称,第二个参数负责提供队列的特性(现在用不到,所以必须为NULL)。当队列创建好以后,就可以给他指派任务。

  • 并发队列

并发调度队列适合那些可以并行执行的任务。并发队列也遵从先进先出(FIFO)的规范,且任务可以在前一个任务结束前就开始执行。每一次运行同一个程序,并发任务的数量可能是不一样的,因为它会根据其它运行的任务在不同时间变化。

说明:如果需要确保每次运行的任务数量都是一样的,可以通过线程API来手动管理线程。

三种并发队列:

(1)高优先级(high):优先级选项是DISPATCH_QUEUE_PRIORITY_HIGH

(2)默认优先级(default):优先级选项是DISPATCH_QUEUE_PRIORITY_DEFAULT

(3)低优先级(low):优先级选项是DISPATCH_QUEUE_PRIORITY_LOW

如果想要引用他们,可以调用dispatch_get_global_queue方法。

代码:

dispatch_queue_t myQueue;
myQueue = dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

说明:第一个参数是优先级选项,对应不同的优先级。第二个参数暂时都用0。因为它们都是全局的,所以无需为他们管理内存。不需要保留这些队列的引用,在需要的时候使用函数来访问就行了。

  • 主队列

使用dispatch_get_main_queue可以访问与应用程序主线程相关的连续队列。

dispatch_queue_t main_queue = dispatch_get_current_queue(void);

因为这个队列与主线程相关,所以必须小心安排这个队列中的任务顺序,否则它们可能会阻塞主应用程序运行。通常要以同步的方式使用这个队列,提交过个任务并在它们操作完毕后执行一些动作。

  • 获取当前队列

可以通过dispatch_get_current_queue()来找出当前运行的队列代码块。如果在代码块对象之外调用了这个函数,则它将返回主队列。

 dispatch_queue_t myQueue = dispatch_get_current_queue();

2.2.2 队列的内存管理

调度队列是引用计数对象。可以使用dispatch_retain()和dispatch_release来修改队列的保留计数值。它们与一般的retain和release语句相似。你只能对你自己创建的队列使用这些函数,而无法用在全局调度队列上。事实上,如果你向全局队列发送这些消息,是会被忽略的。如果你编写了一个使用了垃圾回收机制的OS X应用程序,那么你必须手动管理这些队列。

1.队列的上下文(context)

“在软件工程中,上下文是一种属性的有序序列,它们为驻留在环境内的对象定义环境。在对象的激活过程中创建上下文,对象被配置为要求某些自动服务,如同步、事务、实时激活、安全性等等。又比如计算机技术中,相对于进程而言,上下文就是进程执行时的环境。具体来说就是各个变量和数据,包括所有的寄存器变量、进程打开的文件、内存信息等。”

你可以向调度对象(包括调度队列)指派全局数据上下文,可以在上下文中指派任意类型的数据,比如Objective-C对象或指针。系统只知道上下文包含了于队列相关的数据,上下文数据的内存管理只能由你来做。在为上下文分配内存的时候,可以使用dispatch_set_context()和dispatch_get_context()函数。

代码:

NSMutableDictionary *myContext = [[NSMutableDictionary alloc] initWithCapacity:5];
[myContext setObject:@“My Context” forKey:@“title”];
[myContext setObject:[NSNumber numberWithInt:0] forKey:@“value”];
dispatch_set_context(_serial_queue, (__bridge_retained void *) myContext);

在这个实例中,我们创建一个字典来存储上下文,当然也可以使用其它的指针类型。分配好内存之后就可以使用。

在最后一行代码中,我们必须保证对象是有效的,所以使用了__bridge_retained来给myContext的保留计数器的值加1。

  • 清理函数

设置完上下文对象的数据之后,不需要真的知道上下文对象在何时何地会被弃用。可以让对象在它弃用的时候调用一个函数,就像类里面的dealloc函数。函数的格式应该如下所示:

void function_name(void *context);

我们将创建一个会在上下文对象弃用时调用的示例函数,通常称为终结器(finalizer)函数。

void myFinalizerFunction(void *context)
{
     NSLog(@“myFinalizerFunction”);
     NSMutableDictionary *theData = (__bridge_transfer NSMutableDictionary *)context;
     [theData removeAllObjects];
}
 __bridge_transfer 关键字:

这个关键字将对象的内存管理由全局释放池变换成了我们的函数。当我们的函数结束后,ARC将会给它的保留计数的值减1,如果保留计数的值被减到了0,对象将会被释放。如果对象没有被释放,myContext将会一直留在内存中。

如何在代码块中访问上下文内容?

NSMutableDictionary *myContext = (__bridge NSMutableDictionary *)dispatch_get_context(dispatch_get_current_queue());

这行代码中添加了__bridge关键字。是用来告诉ARC,我们并不想自己管理上下文的内存,而是想交给系统来管理。

  • 添加任务

有两种方法可以向队列中添加任务:

(1)同步:队列会一直等待前面任务结束。

(2)异步:添加任务后,不必等待任务,函数会立刻返回。推荐优先使用这种方式,因为它不会阻塞其他代码的运行。

可以选择向队列添加代码块或函数。一共有四个调度函数,分别是代码块和函数各自的同步与异步方式。

注意:为了避免出现死锁,不要给运行在同一队列中的任务调用dispatch_sync或dispatch_sync_f函数。

2.调度程序

(1)通过代码块添加任务

代码块必须是dispatch_block_t这样的类型,要定义为没有参数和返回值才行。

typedef void(^dispatch_block_t)(void);

先添加异步代码块。这个函数拥有两个参数,分别是队列和代码块。

dispatch_async(_serial_queue, ^{
     NSLog(@“Serial Task 1”);
});

如果是同步添加,使用dispatch_sync函数。

(2)通过函数添加任务

函数的标准原型必须要像下面这样:

void fucntion_name(void argument)

示例函数:

void myDispatchFunction(void *argument)
{
     NSLog(@“Serial Task %@”,(__bridge NSNumber *)argument);
     NSMutableDictionary *context = (__bridge NSMutableDictionary *)dispatch_get_context(dispatch_get_current_queue());
     NSNumber *value = [context objectForKey:@“value”];
     NSLog(@“value = %@“,value);
}
  • 向队列添加这个函数

调用函数拥有三个参数:队列、需要传递的任意上下文以及函数。如果没有信息要发送给函数,也可以只传递一个NULL值。

dispatch_async_f(_serial_queue, (__bridge void *) [NSNumber numberWithInt:3], (dispatch_function_t)myDispatchFunction);

如果想以同步的方式添加到队列中,请调用dispatch_sync_f函数。

  • 暂停队列

如果出于某个原因要暂停队列,请调用dispatch_susend()函数并传递队列名称。

dispatch_suspend(_serial_queue);

  • 重新启用队列

队列暂停之后,可以调用dispatch_resume()函数来重新启用。

dispatch_resume(_serial_queue);

2.3 操作队列

Objective-C提供一些被称为操作(operation)的API,使队列在Objective-C层级上使用起来更加简单。

如果想要使用操作,首先需要创建一个操作对象,然后将其指派给操作队列,并让队列执行它。一共有三种创建队列的方式。

(1)NSInvocationOperation:

如果已经有一个可以完成工作的类,并且想要在队列上执行它,可以尝试使用这种方法。

(2)NSBlockOperation:

类似于包含了需要执行代码块的dispatch_async函数。

(3)自定义操作:

如果需要更灵活的操作类型,可以创建自己的自定义类型。必须通过NSOperation子类来定义你的操作。

2.3.1 创建调用操作(invocation operation)

NSInvocationOperation会为执行任务的类调用选择器。因此你拥有 一个包含所需方法的类,使用这种方式来创建会非常方便。

@implementation MyCustomClass
-(NSOperation *)operationWithData:(id)data
{
     return [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(myWorkerMethod:) object:data];
}

//This is the method that does the actual work
-(void)myWorkerMethod:(id)data
{
     NSLog(@“My Worker Method %@“,data);
}
@end

一旦向队列中添加了操作,任务即将执行时便会调用类里面的myWorkerMethod:方法。

  • 创建代码块操作 (block operation)

如果你有一个需要执行的代码块,那么可以创建这个操作并让队列执行它。

NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWihBlock:^{
     //Do my work
}];

一旦创建了第一个代码块,你便可以通过addExecutionBlock:方法继续添加更多的代码块。根据队列的类型(连续的还是并发的),代码块会分别以连续或者并发的方式进行。

[blockOperation addExecutionBlock:^{
     //dow some more work
}];
  • 向队列中添加操作

一旦创建了操作,你就需要向队列中添加代码块。NSOperationQueue一般会并发执行。它具有相关性,因此如果某操作是基于其他操作的,它们会相应地执行。

如果要确保你的操作是连续执行的,可以设置最大并发操作数是1,这样任务就会按照先入先出的规范执行。在向队列添加操作之前,需要某个方法来引用到那个队列。可以创建一个新队列或使用之前已经定义过的队列(比如当前运行的队列)。

NSOperationQueue *currentQueue = [NSOperationQueue currentQueue];

或主队列:

NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];

以下就是创建队列的代码:

NSOperationQueue *_operationQueue = [[NSOperationQueue alloc] init];
//添加操作
[_operationQueue addOperation:blockOperation];

也可以添加需要执行的代码块来替代操作对象

[_operationQueue addOperationWithBlock:^{
     NSLog(“My Block”);
}];

一旦队列中添加了操作,它就会被安排进度并执行。

版权声明:本文为博主原创文章,转载请注明出处。

时间: 2024-10-10 20:15:18

代码块与并发性的相关文章

代码块和并发性

代码块对象: 通常称:代码块,是对C语言的扩展,,除了函数中的代码,其害包含变量绑定.代码块有时也被称为闭包(closure).两种绑定类型:自动型和托管型.自动型使用的是栈中的内存,而托管绑定是通过堆创建的. 代码块和函数指针: 代码块特征:1.返回类型可以手动声明也可以由编译器推导.2.具有指定类型的参数列表.3.有用名称. 声明一个函数指针:void(*my_func)(void); 这是很基础的函数指针,它没有参数和返回结果,只要把 * 替换成 ^ 就可以把它转换成一个代码块的定义了.如

Objective-c 05 类别 类扩展 委托 非正式协议 协议 代码块 并发性 队列

类别 为已经存在的类添加行为时,通常采用创建子类的方法,不过有时子类并不方便, 比如:创建NSString的子类,但是NSString实际上只是一个类簇的表面形式.因而为这样的类创建子类会非常困难.在其他情况下, 也许可以创建它的子类,但是用到的工具集和库无法帮你处理新类的对象的.例如:当使用stringWithFormat:类方法生成新字符串时,你创建的 NSString类的新子类就无法返回.   利用Objective-C的动态运行时分配机制,可以为现有的类添加新的方法.这些新的方法在Obj

编码最小单位:代码块。

代码块:完成一定功能的一系列的代码 ,或者是几行代码 或者是 连续调用几个函数. 将代码分割成代码块,或者通过分割成单独的函数 又或者 仅是录入几行空白部分实现. 在完成一定功能的代码块中最好不要加入其他功能的代码 且  要注意代码块的顺序性   尤其是在代码块间存在着数据共享. 上文源于实际开发中.且代码排查花费了12人时. 伪代码举例: ①.存在TimerManager类,其属性有List<ActionTimer> list.方法有removeTimer(id:String).run():

用亲身经历告诉你,在你的并发程序代码块中,最好最好不要有引用类型

用亲身经历告诉你,在你的并发程序代码块中,最好最好不要有引用类型,必要的string类型还是可以的.目前正在把自己前段时间写的并发程序放到自己的项目中,以提高速度,由于我的项目是与移动对接的,因此,询问了移动的接口能不能响应高并发之后,并且得到了肯定的答复,我就开始着手demo设计了,但是一直出错,一直有问题,后来想到,可能是引用类型的问题,因为我的并发代码需要不断地给一个类实例对象的某个字段赋值,然后去调用移动接口,我就捉摸着,可能是因为并发太快了,然后这个引用类型赋值的之后,没有来的及更换内

并发编程(5):锁对象、同步代码块

1.同步代码块 使用synchronized声明的方法在某些情况下是有弊端的,比如A线程调用同步的方法执行一个很长时间的任务,那么B线程就必须等待比较长的时间才能执行,这样的情况下可以使用synchronized代码块去优化代码执行时间,也就是通常所说的减小锁的粒度. 代码 public class Demo7 { public void doLongTimeTask(){ try { System.out.println("当前线程开始:" + Thread.currentThrea

Java并发学习之十四——使用Lock同步代码块

本文是学习网络上的文章时的总结,感谢大家无私的分享. Java提供另外的机制用来同步代码块.它比synchronized关键字更加强大.灵活.Lock 接口比synchronized关键字提供更多额外的功能.在使用Lock时需要注意的是要释放Lock锁. package chapter2; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /* * 打印队列 */ pu

深入了解 Scala 并发性

2003 年,Herb Sutter 在他的文章 “The Free Lunch Is Over” 中揭露了行业中最不可告人的一个小秘密,他明确论证了处理器在速度上的发展已经走到了尽头,并且将由全新的单芯片上的并行 “内核”(虚拟 CPU)所取代.这一发现对编程社区造成了不小的冲击,因为正确创建线程安全的代码,在理论而非实践中,始终会提高高性能开发人员的身价,而让各公司难以聘用他们.看上去,仅有少数人充分理解了 Java 的线程模型.并发 API 以及 “同步” 的含义,以便能够编写同时提供安全

JVM 并发性: Java 和 Scala 并发性基础

处理器速度数十年来一直持续快速发展,并在世纪交替之际走到了终点.从那时起,处理器制造商更多地是通过增加核心来提高芯片性能,而不再通过增加时钟速率来提高芯片性能.多核系统现在成为了从手机到企业服务器等所有设备的标准,而这种趋势可能继续并有所加速.开发人员越来越需要在他们的应用程序代码中支持多个核心,这样才能满足性能需求. 在本系列文章中,您将了解一些针对 Java 和 Scala 语言的并发编程的新方法,包括 Java 如何将 Scala 和其他基于 JVM 的语言中已经探索出来的理念结合在一起.

JVM 并发性: 阻塞还是不阻塞?

在任何并发性应用程序中,异步事件处理都至关重要.事件来源可能是不同的计算任务.I/O 操作或与外部系统的交互.无论来源是什么,应用程序代码都必须跟踪事件,协调为响应事件而采取的操作.Java 应用程序可采用两种基本的异步事件处理方法:该应用程序有一个协调线程等待事件,然后采取操作,或者事件可在完成时直接执行某项操作(通常采取执行应用程序所提供的代码的方式).让线程等待事件的方法被称为阻塞 方法.让事件执行操作.线程无需显式等待事件的方法被称为非阻塞 方法. 旧的 java.util.concur