iOS并发编程笔记,包含GCD,Operation Queues,Run Loops,如何在后台绘制UI,后台I/O处理,最佳安全实践避免互斥锁死锁优先级反转等,以及如何使用GCD监视进程文件文件夹,并发测试的方案等

iOS并发编程笔记,包含GCD,Operation Queues,Run Loops,如何在后台绘制UI,后台I/O处理,最佳安全实践避免互斥锁死锁优先级反转等,以及如何使用GCD监视进程文件文件夹,并发测试的方案等

线程

使用Instruments的CPU strategy view查看代码如何在多核CPU中执行。创建线程可以使用POSIX 线程API,或者NSThread(封装POSIX 线程API)。下面是并发4个线程在一百万个数字中找最小值和最大值的pthread例子:

#import <pthread.h>

struct threadInfo {
     uint32_t * inputValues;
     size_t count;
};

struct threadResult {
     uint32_t min;
     uint32_t max;
};

void * findMinAndMax(void *arg)
{
     struct threadInfo const * const info = (struct threadInfo *) arg;
     uint32_t min = UINT32_MAX;
     uint32_t max = 0;
     for (size_t i = 0; i < info->count; ++i) {
          uint32_t v = info->inputValues[i];
          min = MIN(min, v);
          max = MAX(max, v);
     }
     free(arg);
     struct threadResult * const result = (struct threadResult *) malloc(sizeof(*result));
     result->min = min;
     result->max = max;
     return result;
}

int main(int argc, const char * argv[])
{
     size_t const count = 1000000;
     uint32_t inputValues[count];

     // 使用随机数字填充 inputValues
     for (size_t i = 0; i < count; ++i) {
          inputValues[i] = arc4random();
     }

     // 开始4个寻找最小值和最大值的线程
     size_t const threadCount = 4;
     pthread_t tid[threadCount];
     for (size_t i = 0; i < threadCount; ++i) {
          struct threadInfo * const info = (struct threadInfo *) malloc(sizeof(*info));
          size_t offset = (count / threadCount) * i;
          info->inputValues = inputValues + offset;
          info->count = MIN(count - offset, count / threadCount);
          int err = pthread_create(tid + i, NULL, &findMinAndMax, info);
          NSCAssert(err == 0, @"pthread_create() failed: %d", err);
     }
     // 等待线程退出
     struct threadResult * results[threadCount];
     for (size_t i = 0; i < threadCount; ++i) {
          int err = pthread_join(tid[i], (void **) &(results[i]));
          NSCAssert(err == 0, @"pthread_join() failed: %d", err);
     }
     // 寻找 min 和 max
     uint32_t min = UINT32_MAX;
     uint32_t max = 0;
     for (size_t i = 0; i < threadCount; ++i) {
          min = MIN(min, results[i]->min);
          max = MAX(max, results[i]->max);
          free(results[i]);
          results[i] = NULL;
     }

     NSLog(@"min = %u", min);
     NSLog(@"max = %u", max);
     return 0;
}

使用NSThread来写

@interface FindMinMaxThread : NSThread
@property (nonatomic) NSUInteger min;
@property (nonatomic) NSUInteger max;
- (instancetype)initWithNumbers:(NSArray *)numbers;
@end

@implementation FindMinMaxThread {
     NSArray *_numbers;
}

- (instancetype)initWithNumbers:(NSArray *)numbers
{
     self = [super init];
     if (self) {
          _numbers = numbers;
     }
     return self;
}

- (void)main
{
     NSUInteger min;
     NSUInteger max;
     // 进行相关数据的处理
     self.min = min;
     self.max = max;
}
@end

//启动一个新的线程,创建一个线程对象
SMutableSet *threads = [NSMutableSet set];
NSUInteger numberCount = self.numbers.count;
NSUInteger threadCount = 4;
for (NSUInteger i = 0; i < threadCount; i++) {
     NSUInteger offset = (count / threadCount) * i;
     NSUInteger count = MIN(numberCount - offset, numberCount / threadCount);
     NSRange range = NSMakeRange(offset, count);
     NSArray *subset = [self.numbers subarrayWithRange:range];
     FindMinMaxThread *thread = [[FindMinMaxThread alloc] initWithNumbers:subset];
     [threads addObject:thread];
     [thread start];
}

Grand Central Dispatch

GCD概要

  • 和operation queue一样都是基于队列的并发编程API,他们通过集中管理大家协同使用的线程池。
  • 公开的5个不同队列:运行在主线程中的main queue,3个不同优先级的后台队列(High Priority Queue,Default Priority Queue,Low Priority Queue),以及一个优先级更低的后台队列Background Priority Queue(用于I/O)
  • 可创建自定义队列:串行或并列队列。自定义一般放在Default Priority Queue和Main Queue里。

dispatch_once用法

+ (UIColor *)boringColor;
{
     static UIColor *color;
     //只运行一次
     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
          color = [UIColor colorWithRed:0.380f green:0.376f blue:0.376f alpha:1.000f];
     });
     return color;
}

延后执行

使用dispatch_after

- (void)foo
{
     double delayInSeconds = 2.0;
     dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));
     dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
          [self bar];
     });
}

GCD队列

队列默认是串行的,只能执行一个单独的block,队列也可以是并行的,同一时间执行多个block

- (id)init;
{
     self = [super init];
     if (self != nil) {
          NSString *label = [NSString stringWithFormat:@"%@.isolation.%p", [self class], self];
          self.isolationQueue = dispatch_queue_create([label UTF8String], 0);

          label = [NSString stringWithFormat:@"%@.work.%p", [self class], self];
          self.workQueue = dispatch_queue_create([label UTF8String], 0);
     }
     return self;
}

多线程并发读写同一个资源

//创建队列
self.isolationQueue = dispatch_queue_create([label UTF8String], DISPATCH_QUEUE_CONCURRENT);
//改变setter
- (void)setCount:(NSUInteger)count forKey:(NSString *)key
{
     key = [key copy];
     //确保所有barrier都是async异步的
     dispatch_barrier_async(self.isolationQueue, ^(){
          if (count == 0) {
               [self.counts removeObjectForKey:key];
          } else {
               self.counts[key] = @(count);
          }
     });
}

都用异步处理避免死锁,异步的缺点在于调试不方便,但是比起同步容易产生死锁这个副作用还算小的。

异步API写法

设计一个异步的API调用dispatch_async(),这个调用放在API的方法或函数中做。让API的使用者设置一个回调处理队列

- (void)processImage:(UIImage *)image completionHandler:(void(^)(BOOL success))handler;
{
     dispatch_async(self.isolationQueue, ^(void){
          // do actual processing here
          dispatch_async(self.resultQueue, ^(void){
               handler(YES);
          });
     });
}

dispatch_apply进行快速迭代

for (size_t y = 0; y < height; ++y) {
     for (size_t x = 0; x < width; ++x) {
          // Do something with x and y here
     }
}
//使用dispatch_apply可以运行的更快
dispatch_apply(height, dispatch_get_global_queue(0, 0), ^(size_t y) {
     for (size_t x = 0; x < width; x += 2) {
          // Do something with x and y here
     }
});

Block组合

dispatch_group_t group = dispatch_group_create();

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^(){
     // 会处理一会
     [self doSomeFoo];
     dispatch_group_async(group, dispatch_get_main_queue(), ^(){
          self.foo = 42;
     });
});
dispatch_group_async(group, queue, ^(){
     // 处理一会儿
     [self doSomeBar];
     dispatch_group_async(group, dispatch_get_main_queue(), ^(){
          self.bar = 1;
     });
});

// 上面的都搞定后这里会执行一次
dispatch_group_notify(group, dispatch_get_main_queue(), ^(){
     NSLog(@"foo: %d", self.foo);
     NSLog(@"bar: %d", self.bar);
});

如何对现有API使用dispatch_group_t

//给Core Data的-performBlock:添加groups。组合完成任务后使用dispatch_group_notify来运行一个block即可。
- (void)withGroup:(dispatch_group_t)group performBlock:(dispatch_block_t)block
{
     if (group == NULL) {
          [self performBlock:block];
     } else {
          dispatch_group_enter(group);
          [self performBlock:^(){
               block();
               dispatch_group_leave(group);
          }];
     }
}

//NSURLConnection也可以这样做
+ (void)withGroup:(dispatch_group_t)group
     sendAsynchronousRequest:(NSURLRequest *)request
     queue:(NSOperationQueue *)queue
     completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler
{
     if (group == NULL) {
          [self sendAsynchronousRequest:request
               queue:queue
               completionHandler:handler];
     } else {
          dispatch_group_enter(group);
          [self sendAsynchronousRequest:request
                    queue:queue
                    completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
               handler(response, data, error);
               dispatch_group_leave(group);
          }];
     }
}

注意事项

  • dispatch_group_enter() 必须运行在 dispatch_group_leave() 之前。
  • dispatch_group_enter() 和 dispatch_group_leave() 需要成对出现的

用GCD监视进程

NSRunningApplication *mail = [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.mail"];
if (mail == nil) {
     return;
}
pid_t const pid = mail.processIdentifier;
self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid, DISPATCH_PROC_EXIT, DISPATCH_TARGET_QUEUE_DEFAULT);
dispatch_source_set_event_handler(self.source, ^(){
     NSLog(@"Mail quit.");
});
//在事件源传到你的事件处理前需要调用dispatch_resume()这个方法
dispatch_resume(self.source);

监视文件夹内文件变化

NSURL *directoryURL; // assume this is set to a directory
int const fd = open([[directoryURL path] fileSystemRepresentation], O_EVTONLY);
if (fd < 0) {
     char buffer[80];
     strerror_r(errno, buffer, sizeof(buffer));
     NSLog(@"Unable to open "%@": %s (%d)", [directoryURL path], buffer, errno);
     return;
}
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd,
DISPATCH_VNODE_WRITE | DISPATCH_VNODE_DELETE, DISPATCH_TARGET_QUEUE_DEFAULT);
dispatch_source_set_event_handler(source, ^(){
     unsigned long const data = dispatch_source_get_data(source);
     if (data & DISPATCH_VNODE_WRITE) {
          NSLog(@"The directory changed.");
     }
     if (data & DISPATCH_VNODE_DELETE) {
          NSLog(@"The directory has been deleted.");
     }
});
dispatch_source_set_cancel_handler(source, ^(){
     close(fd);
});
self.source = source;
dispatch_resume(self.source);
//还要注意需要用DISPATCH_VNODE_DELETE 去检查监视的文件或文件夹是否被删除,如果删除了就停止监听

GCD版定时器

dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0, 0, DISPATCH_TARGET_QUEUE_DEFAULT);
dispatch_source_set_event_handler(source, ^(){
     NSLog(@"Time flies.");
});
dispatch_time_t start
dispatch_source_set_timer(source, DISPATCH_TIME_NOW, 5ull * NSEC_PER_SEC,100ull * NSEC_PER_MSEC);
self.source = source;
dispatch_resume(self.source);

GCD深入操作

  • 缓冲区:dispatch_data_t基于零碎的内存区域,使用dispatch_data_apply来遍历,还可以用dispatch_data_create_subrange来创建一个不做任何拷贝的子区域
  • I/O调度:使用GCD提供的dispatch_io_read,dispatch_io_write和dispatch_io_close
  • 测试:使用dispatch_benchmark小工具
  • 原子操作: libkern/OSAtomic.h里可以查看那些函数,用于底层多线程编程。

Operation Queues

  • Operation Queue是在GCD上实现了一些方便的功能。
  • NSOperationQueue有主队列和自定义队列两种类型队列。主队列在主线程上运行,自定义队列在后台。
  • 重写main方法自定义自己的operations。较简单,不需要管理isExecuting和isFinished,main返回时operation就结束了。
@implementation YourOperation
     - (void)main
     {
          // 进行处理 ...
     }
@end
  • 重写start方法能够获得更多的控制权,还可以在一个操作中执行异步任务
@implementation YourOperation
     - (void)start
     {
          self.isExecuting = YES;
          self.isFinished = NO;
          // 开始处理,在结束时应该调用 finished ...
     }

     - (void)finished
     {
          self.isExecuting = NO;
          self.isFinished = YES;
     }
@end
//使操作队列有取消功能,需要不断检查isCancelled属性
- (void)main
{
     while (notDone && !self.isCancelled) {
          // 进行处理
     }
}
  • 定义好operation类以后,将一个operation加到队列里:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
YourOperation *operation = [[YourOperation alloc] init];
[queue addOperation:operation];
  • 如果是在主队列中进行一个一次性任务,可以将block加到操作队列
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
     // 代码...
}];
  • 通过maxConcurrentOperationCount属性控制一个特定队列中并发执行操作的数量。设置为1就是串行队列。
  • 对operation优先级排序,指定operation之间的依赖关系。
//确保operation1和operation2是在intermediateOperation和finishOperation之前执行
[intermediateOperation addDependency:operation1];
[intermediateOperation addDependency:operation2];
[finishedOperation addDependency:intermediateOperation];

Run Loops

  • Run loop比GCD和操作队列要容易,不必处理并发中复杂情况就能异步执行。
  • 主线程配置main run loop,其它线程默认都没有配置run loop。一般都在主线程中调用后分配给其它队列。如果要在其它线程添加run loop至少添加一个input source,不然一运行就会退出。

在后台操作UI

使用操作队列处理

//weak引用参照self避免循环引用,及block持有self,operationQueue retain了block,而self有retain了operationQueue。
__weak id weakSelf = self;
[self.operationQueue addOperationWithBlock:^{
     NSNumber* result = findLargestMersennePrime();
     [[NSOperationQueue mainQueue] addOperationWithBlock:^{
          MyClass* strongSelf = weakSelf;
          strongSelf.textLabel.text = [result stringValue];
     }];
}];

drawRect在后台绘制

drawRect:方法会影响性能,所以可以放到后台执行。

//使用UIGraphicsBeginImageContextWithOptions取代UIGraphicsGetCurrentContext:方法
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
// drawing code here
UIImage *i = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return i;

可以把这个方法运用到table view中,使table view的cell在滚出边界时能在didEndDisplayingCell委托方法中取消。WWDC中有讲解:Session 211 -- Building Concurrent User Interfaces on iOS https://developer.apple.com/videos/wwdc/2012/

还有个使用CALayer里drawsAsynchronously属性的方法。不过有时work,有时不一定。

网络异步请求

网络都要使用异步方式,但是不要直接使用dispatch_async,这样没法取消这个网络请求。dataWithContentsOfURL:的超时是30秒,那么这个线程需要干等到超时完。解决办法就是使用NSURLConnection的异步方法,把所有操作转化成operation来执行。NSURLConnection是通过run loop来发送事件的。AFNetworking是建立一个独立的线程设置一个非main run loop。下面是处理URL连接重写自定义operation子类里的start方法

- (void)start
{
     NSURLRequest* request = [NSURLRequest requestWithURL:self.url];
     self.isExecuting = YES;
     self.isFinished = NO;
     [[NSOperationQueue mainQueue] addOperationWithBlock:^
     {
          self.connection = [NSURLConnectionconnectionWithRequest:request
               delegate:self];
     }];
}

重写start方法需要管理isExecuting和isFinished状态。下面是取消操作的方法

- (void)cancel
{
     [super cancel];
     [self.connection cancel];
     self.isFinished = YES;
     self.isExecuting = NO;
}
//连接完成发送回调
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
     self.data = self.buffer;
     self.buffer = nil;
     self.isExecuting = NO;
     self.isFinished = YES;
}

后台处理I/O

异步处理文件可以使用NSInputStream。官方文档:http://developer.apple.com/library/ios/#documentation/FileManagement/Conceptual/FileSystemProgrammingGUide/TechniquesforReadingandWritingCustomFiles/TechniquesforReadingandWritingCustomFiles.html 实例:https://github.com/objcio/issue-2-background-file-io

@interface Reader : NSObject
- (void)enumerateLines:(void (^)(NSString*))block
     completion:(void (^)())completion;
- (id)initWithFileAtPath:(NSString*)path;

//采用main run loop的事件将数据发到后台操作线程去处理
- (void)enumerateLines:(void (^)(NSString*))block
completion:(void (^)())completion
{
     if (self.queue == nil) {
          self.queue = [[NSOperationQueue alloc] init];
          self.queue.maxConcurrentOperationCount = 1;
     }
     self.callback = block;
     self.completion = completion;
     self.inputStream = [NSInputStream inputStreamWithURL:self.fileURL];
     self.inputStream.delegate = self;
     [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
     forMode:NSDefaultRunLoopMode];
          [self.inputStream open];
     }
@end

//input stream在主线程中发送代理消息,接着就可以在操作队列加入block操作
- (void)stream:(NSStream*)stream handleEvent:(NSStreamEvent)eventCode
{
     switch (eventCode) {
          ...
          case NSStreamEventHasBytesAvailable: {
               NSMutableData *buffer = [NSMutableData dataWithLength:4 * 1024];
               NSUInteger length = [self.inputStream read:[buffer mutableBytes]
                    maxLength:[buffer length]];
               if (0 < length) {
                    [buffer setLength:length];
                    __weak id weakSelf = self;
                    [self.queue addOperationWithBlock:^{
                         [weakSelf processDataChunk:buffer];
                    }];
               }
               break;
          }
          ...
     }
}

//处理数据chunk,原理就是把数据切成很多小块,然后不断更新和处理buffer缓冲区,逐块读取和存入方式来处理大文件响应快而且内存开销也小。
- (void)processDataChunk:(NSMutableData *)buffer;
{
     if (self.remainder != nil) {
          [self.remainder appendData:buffer];
     } else {
          self.remainder = buffer;
     }
     [self.remainder obj_enumerateComponentsSeparatedBy:self.delimiter
               usingBlock:^(NSData* component, BOOL last) {
          if (!last) {
               [self emitLineWithData:component];
          } else if (0 < [component length]) {
               self.remainder = [component mutableCopy];
          } else {
               self.remainder = nil;
          }
     }];
}

并发开发会遇到的困难问题

多个线程访问共享资源

比如两个线程都会把计算结果写到一个整型数中。为了防止,需要一种互斥机制来访问共享资源

互斥锁

同一时刻只能有一个线程访问某个资源。某线程要访问某个共享资源先获得共享资源的互斥锁,完成操作再释放这个互斥锁,然后其它线程就能访问这个共享资源。

还有需要解决无序执行问题,这时就需要引入内存屏障。

在Objective-C中如果属性声明为atomic就能够支持互斥锁,但是因为加解锁会有性能代价,所以一般是声明noatomic的。

死锁

当多个线程在相互等待对方锁结束时就会发生死锁,程序可能会卡住。

void swap(A, B)
{
     lock(lockA);
     lock(lockB);
     int a = A;
     int b = B;
     A = b;
     B = a;
     unlock(lockB);
     unlock(lockA);
}
//一般没问题,但是如果两个线程使用相反的值同时调用上面这个方法就可能会死锁。线程1获得X的一个锁,线程2获得Y的一个锁,它们会同时等待另一个锁的释放,但是却是没法等到的。
swap(X, Y); // 线程 1
swap(Y, X); // 线程 2

为了防止死锁,需要使用比简单读写锁更好的办法,比如write preferencehttp://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock,或read-copy-update算法http://en.wikipedia.org/wiki/Read-copy-update

优先级反转

运行时低优先级任务由于先取得了释放了锁的共享资源而阻塞了高优先级任务,这种情况叫做优先级反转

最佳安全实践避免问题的方法

从主线程中取到数据,利用一个操作队列在后台处理数据,完后返回后台队列中得到的数据到主队列中。这样的操作不会有任何锁操作。

并发测试

除非注明,均为Starming星光社原创,转载请注明本文地址:http://www.starming.com/index.php?v=index&view=73

时间: 2024-10-13 01:50:28

iOS并发编程笔记,包含GCD,Operation Queues,Run Loops,如何在后台绘制UI,后台I/O处理,最佳安全实践避免互斥锁死锁优先级反转等,以及如何使用GCD监视进程文件文件夹,并发测试的方案等的相关文章

iOS并发编程笔记【转】

线程 使用Instruments的CPU strategy view查看代码如何在多核CPU中执行.创建线程可以使用POSIX 线程API,或者NSThread(封装POSIX 线程API).下面是并发4个线程在一百万个数字中找最小值和最大值的pthread例子: #import <pthread.h> struct threadInfo { uint32_t * inputValues; size_t count; }; struct threadResult { uint32_t min;

Java并发编程笔记 并发概览

并发概览 >>同步 如何同步多个线程对共享资源的访问是多线程编程中最基本的问题之一.当多个线程并发访问共享数据时会出现数据处于计算中间状态或者不一致的问题,从而影响到程序的正确运行.我们通常把这种情况叫做竞争条件(race condition),把并发访问共享数据的代码叫做关键区域(critical section).同步就是使得多个线程顺序进入关键区域从而避免竞争条件的发生. >>线程安全性 编写线程安全的代码的核心是要对状态访问操作进行管理,尤其是对共享的和可变的状态访问. 线

并发编程笔记Outline

Motivation a spilt of parallel programning notes, especially on shared memory parallel architecture, since Java apps paly a great deal on java.util.concurrent. Audience myself, who also suffers in Java Concurrent Programmings. Scope Shared Memory Par

iOS网络编程笔记——Socket编程

一.什么是Socket通信: Socket是网络上的两个程序,通过一个双向的通信连接,实现数据的交换.这个双向连路的一端称为socket.socket通常用来实现客户方和服务方的连接.socket是TCP/IP协议的一个十分流行的编程接口.一个socket由一个IP地址和一个端口号唯一确定.TCP/IP协议的传输层又有两种协议:TCP(传输控制协议)和UDP(用户数据报协议).TCP是基于连接的,而UDP是无连接的:TCP对系统资源的要求较多,而UDP少:TCP保证数据的正确性而UDP可能丢包:

Java并发编程笔记4-线程池

我们使用线程的时候就去创建一个线程,但是就会有一个问题: 如果并发的线程数量非常多,而且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会导致大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务? 线程池正好能解决这样的问题.正如名称所称的那样,线程池管理一个工作者线程的同构池.线程池是与工作队列紧密绑定的.所谓工作队列,其作用是持有所有等待执行的任务. 工作者线程的生活从此轻松起来

Java并发编程笔记 阻塞和中断

>>线程的状态转换 线程的状态转换是线程控制的基础,下面这张图片非常直观的展示了线程的状态转换: 线程间的状态转换: 1. 新建(new):新创建了一个线程对象.2. 可运行(runnable):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法.该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 .3. 运行(running):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码.4. 阻塞(block

Java并发编程笔记1-竞争条件&amp;初识原子类&amp;可重入锁

我们知道,在多线程访问一个共享变量的时候会发生安全问题. 首先看下面例子: public class Counter { private int count; public void add(){ try{ for (int i = 0;i<200;i++){ Thread.sleep(100); this.count++; System.out.println(this.count); } }catch (Exception e ){ e.printStackTrace(); } } } pu

OC中并发编程的相关API和面临的挑战

OC中并发编程的相关API和面临的挑战(1) 小引 http://www.objc.io/站点主要以杂志的形式,深入挖掘在OC中的最佳编程实践和高级技术,每个月探讨一个主题,每个主题都会有几篇相关的文章出炉,2013年7月份的主题是并发编程,今天挑选其中的第2篇文章(Concurrent Programming: APIs and Challenges)进行翻译,与大家分享一下主要内容.由于内容比较多,我将分两部分翻译(API和难点)完成,翻译中,如有错误,还请指正. 目录 1.介绍 2.OS

《Go并发编程实战》第2版 紧跟Go的1.8版本号

文章作者:郝林(<Go并发编程实战 (第2版)>作者) 最终来了! 经过出版社的各位编辑.校对.排版伙伴与我的N轮PK和共同努力,<Go并发编程实战>第2版的全部内容最终全然确定,并于2017年3月24日交付印刷!当然,印刷也经历了若干流程,以尽量把出错概率压到最低. 从如今開始,第一次印刷出来的数千本书会陆续入库待售!预售也会相继在亚马逊.京东.当当.互动网启动. 这本书在成稿时就受到了非常多业内大咖的关注.批阅和推广.在这里,我由衷地感谢大咖们的给力支持! 一直战斗在云计算技术