在IOS中为什么使用多线程及多线程实现的三种方法

多线程是一个比较轻量级的方法来实现单个应用程序内多个代码执行路径。

在系统级别内,程序并排执行,程序分配到每个程序的执行时间是基于该程序的所需时间和其他程序的所需时间来决定的。

然而,在每个程序内部,存在一个或者多个执行线程,它同时或在一个几乎同时发生的方式里执行不同的任务。

概要提示:

iPhone中的线程应用并不是无节制的,官方给出的资料显示,iPhone OS下的主线程的堆栈大小是1M,第二个线程开始就是512KB,并且该值不能通过编译器开关或线程API函数来更改,只有主线程有直接修改UI的能力

一、线程概述

有些程序是一条直线,起点到终点——如简单的hello world,运行打印完,它的生命周期便结束了,像是昙花一现。

有些程序是一个圆,不断循环直到将它切断——如操作系统,一直运行直到你关机。

一个运行着的程序就是一个进程或者叫做一个任务,一个进程至少包含一个线程,线程就是程序的执行流。

Mac和IOS中的程序启动,创建好一个进程的同时,一个线程便开始运作,这个线程叫做主线程。主线成在程序中的位置和其他线程不同,它是其他线程最终的父线程,且所有的界面的显示操作即AppKit或UIKit的操作必须在主线程进行。

系统中每一个进程都有自己独立的虚拟内存空间,而同一个进程中的多个线程则公用进程的内存空间。

每创建一个新的进成,都需要一些内存(如每个线程有自己的stack空间)和消耗一定的CPU时间。

当多个进成对同一个资源出现争夺的时候需要注意线程安全问题

创建线程

创建一个新的线程就是给进程增加一个执行流,所以新建一个线程需要提供一个函数或者方法作为线程的进口。

1.使用NSThread

NSThread提供了创建线程的路径,还可以提供了监测当前线程是否是主线程的方法使用NSThread创建一个新的线程有两种方式:

1.创建一个NSThread的对象,调用Start方法——使用一个目标对象的方法初始化一个NSThread对象,或者创建一个继承自NSThread的子类,实现起main方法?,然后在直接创建这个子类的对象。

2.使用detachNewThreadSelector:toTarget:withObject:这个类方法创建一个子线程,这个比较直接,直接使用目标对象的方法作为线程启动入口

2.使用NSObject

使用NSObject直接就加入了对多线程的支持,允许对象的某个方法在后台运行。

[my0bj performSelectorInBackground:@selector(doSomething) withObject:nil];

3.POSIX Thread

由于Mac和IOS都是基于Darwin系统,Darwin系统的UNX内核,是基于mach和BSD的,继承了BSD的POSIX接口,所以可以直接使用POSIX线程的相关接口开实现线程
创建线程的接口为 pthread_create, 当然在创建线程之前可以创建好相关线程的属性

——————————————————————————————————————

NSOperation&NSOperationQueue

很多时候我们使用多线程,需要控制线程的并发数,毕竟线程也是需要消耗系统资源的,当程序中同时运行的线程过多时,系统必然变慢,所以很多时候我们会控制同时运行线程的数目
NSOperation可以封装我们的操作,然后将创建好的NSOperation对象放到NSOperationQueue队列中,OperationQueue便开始启动新的线程去执行队列中的操作,OperationQueue的并发数时可以通过如下方式进行设置的:

- (void)setMaxConcurrentOperationCount:(NSInteger)count

GCD时Grand central Dispatch的缩写,是一系列BSD层面的接口。在mac10.6和IOS4.0以后才引入的且现在NSOperation和NSOperationQueue的多线程的实现就是基于GCD的。目前这个特性也被移植到 FreeBSD上了,可以查看libdispatch这个开源项目。

dispatch_queue_t imageDownloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

当然,GCD除了处理多线程外还有很多非常好的功能,其建立在强大的kqueue之上,效率也能够得到保障。

前言

在多线程简介中,我已经说明过了,为了提高界面的流畅度以及用户体验。我们务必要把耗时的操作放到别的线程中去执行,千万不要阻塞主线程。

下面小编给大家带来三种ios多线程编程方法:

NSThread
Grand Centeral Dispatch(GCD)
NSOperation和NSOperationQueue

1.NSThread

这是最轻量级的多线程的方法,使用起来最直观的多线程编程方法。但是因为需要自己管理线程的生命周期,线程同步。经常使用NSThread进行调试,在实际项目中不推荐使用。

?
1
2
3
4
5
6
//获取当前线程
NSThread *current = [NSThread currentThread];
//获取主线程
NSThread *main = [NSThread mainThread];
NSLog(@ "当前线程 --- %@" ,current);
NSLog(@ "主线程 --- %@" ,main);

控制台输出结果:

2015-11-22 22:30:29.572 多线程demo[1289:2925847] 当前线程 --- <NSThread: 0x7fc0e1401dc0>{number = 1, name = main}
2015-11-22 22:30:29.572 多线程demo[1289:2925847] 主线程 --- <NSThread: 0x7fc0e1401dc0>{number = 1, name = main}

从结果我们看出当前的线程就是主线程, number 相当于线程的id, name 是线程的名称,主线程的number就是1

阻塞线程:

?
1
2
3
//阻塞线程3秒
[NSThread sleepForTimeInterval: 3 ];
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow: 3 ]];

2.GCD(Grand Central Dispatch)

GCD是基于C语言底层API实现的一套多线程并发机制,非常的灵活方便,在实际的开发中使用很广泛。

简单来说CGD就是把 操作 放在 队列 中去执行。

你只需定义好操作和队列就可以了,不需要直接控制线程的创建和销毁,线程的生命周期由队列来管理。

队列:负责操作的调度和执行,有先进先出(FIFO)的特点。也就是说先加入队列的操作先执行,后加入的后执行。

队列有两种:

串行队列:

队列中的操作只会按顺序执行,你可以想象成单窗口排队。

并行队列:

队列中的操作可能会并发执行,这取决与操作的类型,你可以想象成多窗口排队。

?
1
2
3
4
//创建串行队列
dispatch_queue_t q = dispatch_queue_create( "my_serial_queue" , DISPATCH_QUEUE_SERIAL);
//创建并行队列
dispatch_queue_t q = dispatch_queue_create( "my_concurrent_queue" , DISPATCH_QUEUE_CONCURRENT);

my_serial_queue和my_concurrent_queue是队列的名字标签,为了与其他的队列区分,在一个项目里面必须是唯一的。

DISPATCH_QUEUE_SERIAL表示串行队列

DISPATCH_QUEUE_CONCURRENT表示并行队列

操作同样也分两种类型:

同步操作:只会按顺序执行,执行顺序是确定的。

异步操作:在串行队列中执行顺序确定,在并行队列中执行顺序不确定

使用block来定义操作要执行的代码,q是已经定义好的,操作要加入的队列

?
1
2
3
4
5
6
7
8
//定义同步操作
dispatch_sync(q, ^{
  //要执行的代码
});
//定义异步操作
dispatch_async(q, ^{
  //要执行的代码 
});

下面我们看一下同步,异步操作加入到串行和并行队列里面,执行的顺序和特点:


(注意: 这里有一点要说明,那就是队列是负责线程的,可以认为是线程池,负责需要开多少条线程,怎么开,线程的生命周期;

同步异步是负责开不开线程的问题,同步并不是保证任务一定在主线程执行,这个很多人都说错了,具体block中的任务在那条线程执行,

需要处决于上下文的。

同步异步的区别是: 有没有创建新的线程,同步不会创建新的线程去执行任务,会阻塞当前的线程,异步会创新新的线程执行任务,不会阻塞当前的

线程;(举个列子:假如当前线程number是2,那么同步任务就会阻塞线程2,等到block执行完毕才会去执行number2中剩下的任务;如果是异步就会创建一条新的线程可能是number3去执行block中的任务,而不会阻塞number2的执行)

串行并行的区别是:串行队列里面的任务都是有序执行的FIFO模式,并行队列类似于多个窗口排队,不能保证任务一定有序。

GCD 用的不好可能导致死硕的问题,在我的下一篇博客中有说明。

)

1.同步操作不管加入到何种队列

?
1
2
3
4
5
6
7
8
9
10
11
12
dispatch_queue_t q_serial = dispatch_queue_create( "my_serial_queue" , DISPATCH_QUEUE_SERIAL);
dispatch_queue_t q_concurrent = dispatch_queue_create( "my_concurrent_queue" , DISPATCH_QUEUE_CONCURRENT);
for ( int i = 0 ; i < 5 ; ++i) {
  dispatch_sync(q_serial, ^{
  NSLog(@ "串行队列里的同步任务 %@ %d" , [NSThread currentThread], i);
  });
}
for ( int i = 0 ; i < 5 ; ++i) {
  dispatch_sync(q_concurrent, ^{
  NSLog(@ "并行队列里的同步任务 %@ %d" , [NSThread currentThread], i);
  });
}

下面是控制台输出结果:

2015-11-23 00:40:36.862 01.GCD演练[1952:3613752] 串行队列里的同步任务 <NSThread: 0x7ff833505450>{number = 1, name = main} 0
2015-11-23 00:40:36.863 01.GCD演练[1952:3613752] 串行队列里的同步任务 <NSThread: 0x7ff833505450>{number = 1, name = main} 1
2015-11-23 00:40:36.863 01.GCD演练[1952:3613752] 串行队列里的同步任务 <NSThread: 0x7ff833505450>{number = 1, name = main} 2
2015-11-23 00:40:36.863 01.GCD演练[1952:3613752] 串行队列里的同步任务 <NSThread: 0x7ff833505450>{number = 1, name = main} 3
2015-11-23 00:40:36.863 01.GCD演练[1952:3613752] 串行队列里的同步任务 <NSThread: 0x7ff833505450>{number = 1, name = main} 4
2015-11-23 00:40:36.863 01.GCD演练[1952:3613752] 并行队列里的同步任务 <NSThread: 0x7ff833505450>{number = 1, name = main} 0
2015-11-23 00:40:36.863 01.GCD演练[1952:3613752] 并行队列里的同步任务 <NSThread: 0x7ff833505450>{number = 1, name = main} 1
2015-11-23 00:40:36.863 01.GCD演练[1952:3613752] 并行队列里的同步任务 <NSThread: 0x7ff833505450>{number = 1, name = main} 2
2015-11-23 00:40:36.864 01.GCD演练[1952:3613752] 并行队列里的同步任务 <NSThread: 0x7ff833505450>{number = 1, name = main} 3
2015-11-23 00:40:36.864 01.GCD演练[1952:3613752] 并行队列里的同步任务 <NSThread: 0x7ff833505450>{number = 1, name = main} 4

2.异步操作只在非主线程的线程执行,在串行队列中异步操作会在新建的线程中按顺序执行。

?
1
2
3
4
5
6
dispatch_queue_t q_serial = dispatch_queue_create( "my_serial_queue" , DISPATCH_QUEUE_SERIAL);
for ( int i = 0 ; i < 5 ; ++i){
  dispatch_async(q_serial, ^{
   NSLog(@ "串行队列 -- 异步任务 %@ %d" , [NSThread currentThread], i);
  });
}

因为是异步操作,所以会新建一个线程。又因为加入到串行队列中,所以所有的操作只会按顺序执行。

2015-11-23 01:03:22.372 01.GCD演练[2081:3627139] 串行队列 -- 异步任务 <NSThread: 0x7fb593d42f50>{number = 2, name = (null)} 0
2015-11-23 01:03:23.373 01.GCD演练[2081:3627139] 串行队列 -- 异步任务 <NSThread: 0x7fb593d42f50>{number = 2, name = (null)} 1
2015-11-23 01:03:24.374 01.GCD演练[2081:3627139] 串行队列 -- 异步任务 <NSThread: 0x7fb593d42f50>{number = 2, name = (null)} 2
2015-11-23 01:03:25.375 01.GCD演练[2081:3627139] 串行队列 -- 异步任务 <NSThread: 0x7fb593d42f50>{number = 2, name = (null)} 3
2015-11-23 01:03:26.376 01.GCD演练[2081:3627139] 串行队列 -- 异步任务 <NSThread: 0x7fb593d42f50>{number = 2, name = (null)} 4

3.异步操作,并行队列

?
1
2
3
4
5
6
dispatch_queue_t q_concurrent = dispatch_queue_create( "my_concurrent_queue" , DISPATCH_QUEUE_CONCURRENT);
for ( int i = 0 ; i < 5 ; ++i){
  dispatch_async(q_concurrent, ^{
   NSLog(@ "并行队列 -- 异步任务 %@ %d" , [NSThread currentThread], i);
  });
}

理论上并行队列会给每一个异步操作新建线程,然后让所有的任务并发执行。但是实际上系统能创建的线程数量是有限的,当创建的线程达到最大线程数以后,后面的异步操作就需要等待前面的操作执行完毕才能得到执行。哪个线程操作执行完毕,就把等待的异步任务安排到哪个线程。直到所有的操作执行完毕。你可以把上述代码的循环次数改成5000就可以观察到此现象。

2015-11-23 01:14:15.282 01.GCD演练[2165:3634728] 并行队列 -- 异步任务 <NSThread: 0x7fb3b841b0a0>{number = 4, name = (null)} 3
2015-11-23 01:14:15.282 01.GCD演练[2165:3634724] 并行队列 -- 异步任务 <NSThread: 0x7fb3b8514da0>{number = 3, name = (null)} 0
2015-11-23 01:14:15.282 01.GCD演练[2165:3634726] 并行队列 -- 异步任务 <NSThread: 0x7fb3b8604db0>{number = 5, name = (null)} 2
2015-11-23 01:14:15.282 01.GCD演练[2165:3634725] 并行队列 -- 异步任务 <NSThread: 0x7fb3b86119d0>{number = 2, name = (null)} 1
2015-11-23 01:14:15.285 01.GCD演练[2165:3634729] 并行队列 -- 异步任务 <NSThread: 0x7fb3b87011f0>{number = 6, name = (null)} 4


有这么1种需求:

首先:分别异步执行2个耗时的操作

其次:等2个异步操作都执行完毕后,再回到主线程执行操作

 

如果想要快速高效地实现上述需求,可以考虑用队列组

dispatch_group_t group =  dispatch_group_create();

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // 执行1个耗时的异步操作

});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // 执行1个耗时的异步操作

});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

    // 等前面的异步操作都执行完毕后,回到主线程...

});



3.NSOperation & NSOperationQueue

虽然GCD的功能已经很强大了,但是它使用的API依然是C语言的。在某些时候,在面向对象的objective-c中使用起来非常的不方便和不安全。

所以苹果公司把GCD中的操作抽象成NSOperation对象,把队列抽象成NSOperationQueue对象。


抽象为NSOperation & NSOperationQueue以后的好处有一下几点:

代码风格统一了,我们不用在面向对象的objective-C中写面对过程的C语言代码了。
我们知道在GCD中操作的执行代码都是写在匿名的block里面,那么我们很难做到给操作设置依赖关系以及取消操作。这些功能都已经封装到NSOperation对象里面了。^-^
NSOperationQueue对象比GCD中队列更加的强大和灵活,比如:设置并发操作数量,取消队列中所有操作。
NSOperation分为NSInvocationOperation和NSBlockOperation

NSInvocationOperation的使用

?
1
2
3
4
5
6
7
8
//首先定义一个NSOperationQueue对象
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector: @selector (operationAction:) object:@ "这里可以穿参数" ];
[queue addOperation:op]; //把操作加入队列中即开始执行
- ( void )operationAction:(id)obj
{
  NSLog(@ "%@ - obj : %@" , [NSThread currentThread], obj);
}

输出为:

2015-11-23 02:55:19.067 多线程demo[2604:3686934] <NSThread: 0x7f9dfa443510>{number = 2, name = (null)} - obj : 这里可以穿参数

NSBlockOperation的使用

?
1
2
3
4
5
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
  [self operationAction:@ "这是NSBlockOperation" ];
}];
[queue addOperation:op];

输出为:

2015-11-23 02:56:11.812 多线程demo[2617:3687872] <NSThread: 0x7fa983f10a50>{number = 2, name = (null)} - obj : 这是NSBlockOperation

设置依赖关系(执行顺序)

?
1
2
3
4
5
6
7
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector: @selector (operationAction:) object:@ "op1" ];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector: @selector (operationAction:) object:@ "op2" ];
//op2在op1之后执行
[op2 addDependency:op1]; //这里需要注意,一定要在addOperation之前设置依赖关系
[queue addOperation:op1];
[queue addOperation:op2];

输出为:

2015-11-23 02:57:40.283 多线程demo[2661:3689737] <NSThread: 0x7fb663e132d0>{number = 2, name = (null)} - obj : op1
2015-11-23 02:57:40.284 多线程demo[2661:3689737] <NSThread: 0x7fb663e132d0>{number = 2, name = (null)} - obj : op2

没有设置依赖关系的输出:

2015-11-23 03:00:45.939 多线程demo[2709:3692307] <NSThread: 0x7fe951d0d8a0>{number = 2, name = (null)} - obj : op2
2015-11-23 03:00:45.939 多线程demo[2709:3692308] <NSThread: 0x7fe951c24720>{number = 3, name = (null)} - obj : op1

到这里你应该发现了,在NSOperation & NSOperationQueue中,我们不需要再像GCD那样定义操作的类型和队列的类型和控制操作的执行顺序了,你只需要直接设定操作的执行顺序就可以了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个多线程的二进制文件和十六进制文件相互转化的示例代码,你可以在 VS2013 进行编译和运行: ```c++ #include <iostream> #include <fstream> #include <string> #include <thread> #include <mutex> using namespace std; // 互斥锁,用于保护文件读写操作 mutex mtx; // 将二进制文件转化为十六进制字符串 void bin2hex(string bin_file_path, string hex_file_path) { ifstream bin_file(bin_file_path, ios::binary); ofstream hex_file(hex_file_path); if (!bin_file || !hex_file) { cout << "文件打开失败!" << endl; return; } // 获取文件大小 bin_file.seekg(0, ios::end); int file_size = bin_file.tellg(); bin_file.seekg(0, ios::beg); // 每次读取 1KB 数据进行转化 const int BUF_SIZE = 1024; char buf[BUF_SIZE]; for (int i = 0; i < file_size; i += BUF_SIZE) { int len = min(file_size - i, BUF_SIZE); bin_file.read(buf, len); // 将每个字节转化为两个十六进制字符 for (int j = 0; j < len; j++) { char hex_str[3]; sprintf_s(hex_str, "%02X", (unsigned char)buf[j]); mtx.lock(); hex_file << hex_str; mtx.unlock(); } } bin_file.close(); hex_file.close(); } // 将十六进制字符串转化为二进制文件 void hex2bin(string hex_file_path, string bin_file_path) { ifstream hex_file(hex_file_path); ofstream bin_file(bin_file_path, ios::binary); if (!hex_file || !bin_file) { cout << "文件打开失败!" << endl; return; } // 每次读取 1KB 数据进行转化 const int BUF_SIZE = 1024; char buf[BUF_SIZE]; while (hex_file.get(buf, BUF_SIZE + 1)) { int len = hex_file.gcount(); // 将每两个十六进制字符转化为一个字节 for (int i = 0; i < len; i += 2) { unsigned char byte = 0; for (int j = 0; j < 2; j++) { byte <<= 4; if (buf[i + j] >= '0' && buf[i + j] <= '9') { byte |= buf[i + j] - '0'; } else if (buf[i + j] >= 'A' && buf[i + j] <= 'F') { byte |= buf[i + j] - 'A' + 10; } else if (buf[i + j] >= 'a' && buf[i + j] <= 'f') { byte |= buf[i + j] - 'a' + 10; } else { cout << "错误的十六进制字符:" << buf[i + j] << endl; return; } } mtx.lock(); bin_file.put(byte); mtx.unlock(); } } hex_file.close(); bin_file.close(); } int main() { // 测试二进制文件转化为十六进制字符串 string bin_file_path = "test.bin"; string hex_file_path = "test.hex"; thread t1(bin2hex, bin_file_path, hex_file_path); // 测试十六进制字符串转化为二进制文件 string hex_file_path2 = "test2.hex"; string bin_file_path2 = "test2.bin"; thread t2(hex2bin, hex_file_path2, bin_file_path2); // 等待两个线程执行完成 t1.join(); t2.join(); return 0; } ``` 代码使用了两个 `thread` 对象,分别表示将二进制文件转化为十六进制字符串和将十六进制字符串转化为二进制文件的操作。在每个线程使用了 `ifstream` 和 `ofstream` 对象来读取和写入文件,并使用了 `mutex` 对象来保护文件读写操作,以避免多个线程同时访问同一个文件导致的数据竞争问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值