关于多线程,是不是开指定n条线程,for循环多线程执行子程序,直到所有任务完成?

&nbsp>&nbsp
&nbsp>&nbsp
&nbsp>&nbsp
几个有关iOS的几个常见问题-----多线程(NSthread、NSOperation、GC
摘要:iOS面试第二个常问问题:多线程。常见分这么几种:①你懂多线程吗?②多线程都有什么?③你常用哪几种多线程?④多线程是用来干什么的?然后进级问题就是:①你觉得NSthread、NSOperation、GCD他们之间的区别是什么?优缺点是啥?下面我就给大家说道说道我的看法。一、什么是多线程。很简单就是让计算机同时干两件事儿,那iOS举例:你打开一个软件,点击一下下载按钮,然后就去浏览其他信息。。。这就是多线程,让消耗时间的任务在后台或者说不影响用户对应用的操作与使用就是就是多线程
iOS面试第二个常问问题:多线程。常见分这么几种:①你懂多线程吗?②多线程都有什么?③你常用哪几种多线程?④多线程是用来干什么的?然后进级问题就是:①你觉得NSthread、NSOperation、GCD他们之间的区别是什么?优缺点是啥?
下面我就给大家说道说道我的看法。
一、什么是多线程。
很简单就是让计算机同时干两件事儿,那iOS举例:你打开一个软件,点击一下下载按钮,然后就去浏览其他信息。。。这就是多线程,让消耗时间的任务在后台或者说不影响用户对应用的操作与使用就是就是多线程。
二、你常用哪几种多线程?
好吧这个是重点、我说我常用的只有NSthread你是不是觉得我很low?但是这是事实啊,我们先说我们需要软件同时进行两个任务的的时候本身就很少啊(因为手机app要尽量减少复杂操作啊,手机在快它也是手机),常用的计时器、图片加载、本地数据上传等等吧。。。计时器你要是用GCD是不是有点大材小用?图片加载,本地书记上传有第三方,至于你真的需要我去在应用中去做一个多线程操作,那么我想说我也不一定非要用GCD吧?好了不多说了,吐槽多余重点了,抱歉。。。。
三、常见几种多线程的用法(我直接把代码复制过来了,对多线程的讲解以及代码注释与代码说明,新建一个工程复制进去很明白的)
①、NSthread多线程#pragma mark -------NSthread 多线程(1)--------
- (void)creat{
#pragma mark - 初始化
// (1)动态创建
// - (id)initWithTarget:(id)target selector:(SEL)selector object:(id)
// selector :线程执行的方法,这个selector最多只能接收一个参数
// target :selector消息发送的对象
// argument : 传给selector的唯一参数,也可以是nil
// 初始化线程
NSThread *thread1 =[[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
// 设置线程的优先级(0.0 -1.0 1.0最高级)
thread1.threadPriority =1;
// 开启线程
[thread1 start];
// (2)静态创建
// + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)
// 调用完毕后,会马上创建并开启新线程
//[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
// (3)隐式创建
//[self performSelectorInBackground:@selector(run) withObject:nil];#pragma mark - 获取当前线程
NSThread *current = [NSThread currentThread];
NSLog(@&%@&,current);
#pragma mark - 获取当前主线程
NSThread *main =[NSThread mainThread];
NSLog(@&%@&,main);
#pragma mark - 暂停进程
//[NSThread sleepForTimeInterval:2];
//NSDate *date = [NSDate dateWithTimeInterval:2 sinceDate:[NSDate date]];
//[NSThread sleepUntilDate:date];
#pragma mark - 线程间的通信
// (1)指定线程上操作
//[self performSelector:@selector(run) onThread:thread1 withObject:nil waitUntilDone:YES];
// (2)在主线程上操作
//[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];
// (3)当前线程上操作
//[self performSelector:@selector(run) withObject:nil];
- (void)run{
for (longi =0; i&10; i++) {
NSLog(@&%ld&,i);
[NSThread sleepForTimeInterval:2];
②、NSthread多线程与NSOperationQueue多线程
#pragma mark -------NSOperation 多线程(2)--------
- (void)creat2{
#pragma mark - NSInvocationOperation创建
_operation =[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run1) object:nil];
//[_operation start];
#pragma mark - NSBlockOperation创建
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^(){
NSLog(@&0执行了一个新的操作,线程:%@&, [NSThread currentThread]);
[operation1 addExecutionBlock:^() {
NSLog(@&1又执行了1个新的操作,线程:%@&, [NSThread currentThread]);
[operation1 addExecutionBlock:^() {
NSLog(@&2又执行了1个新的操作,线程:%@&, [NSThread currentThread]);
[operation1 addExecutionBlock:^() {
NSLog(@&3又执行了1个新的操作,线程:%@&, [NSThread currentThread]);
//[operation1 start];
- (void)run1{
for ( self.a =0; self.a&10; self.a++) {
NSLog(@&%d&,self.a);
#pragma mark -------NSOperationQueue 多线程(3)--------
- (void)creat3{
#pragma mark - 设置没有依赖的队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^(){
NSLog(@&执行第1次操作,线程:%@&, [NSThread currentThread]);
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^(){
NSLog(@&执行第2次操作,线程:%@&, [NSThread currentThread]);
[queue addOperation:operation1];
[queue addOperation:operation2];
#pragma mark - 设置有依赖的队列
[operation1 addDependency:operation2];
[queue addOperation:operation1];
[queue addOperation:operation2];
③、NSOperationQueue多线程(下面副了一个利用多线程加载图片的方式)#pragma mark --------- GCD 多线程(3)----------
- (void)creat4
[self.view addSubview:self.imageView];
#pragma mark - 获得全局并发Dispatch Queue (concurrent dispatch queue)
// 获取默认优先级的全局并发dispatch queue
// 并发dispatch queue可以同时并行地执行多个任务,不过并发queue仍然按先进先出的顺序来启动任务。
// 第一个参数用于指定优先级,分别使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW两个常量来获取高和低优先级的两个第二个参数目前未使用到,默认0即可
dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_tqueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_queue_tqueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
NSLog(@&0&&%@/n1&&%@/n2&&%@&,queue,queue1,queue2);#pragma mark - 创建串行Dispatch Queue (serial dispatch queue)
// 利用dispatch_queue_create函数创建串行queue,两个参数分别是queue名和一组queue属性
dispatch_queue_t queueI;
queueI = dispatch_queue_create(&cn.itcast.queue&, NULL);
NSLog(@&I&&%@&,queueI);#pragma mark - 运行时获得公共Queue
//1& 使用dispatch_get_current_queue函数作为调试用途,或者测试当前queue的标识。在block对象中调用这个函数会返回block提交到的queue(这个时候queue应该正在执行中)。在block对象之外调用这个函数会返回应用的默认并发queue。
//2& 使用dispatch_get_main_queue函数获得应用主线程关联的串行dispatch queue
//3& 使用dispatch_get_global_queue来获得共享的并发queue
#pragma mark - Dispatch Queue的内存管理
// 1& Dispatch Queue和其它dispatch对象(还有dispatch source)都是引用计数的数据类型。当你创建一个串行dispatch queue时,初始引用计数为 1,你可以使用dispatch_retain和dispatch_release函数来增加和减少引用计数。当引用计数到达 0 时,系统会异步地销毁这个queue
// 2& 对dispatch对象(如dispatch queue)retain和release 是很重要的,确保它们被使用时能够保留在内存中。和OC对象一样,通用的规则是如果使用一个传递过来的queue,你应该在使用前retain,使用完之后release
// 3& 你不需要retain或release全局dispatch queue,包括全局并发dispatch queue和main dispatch queue
// 4& 即使你实现的是自动垃圾收集的应用,也需要retain和release创建的dispatch queue和其它dispatch对象。GCD 不支持垃圾收集模型来回收内存
#pragma mark - 添加任务到queue
#pragma mark - 1.添加单个任务到queue
// 1& 异步添加任务
//你可以异步或同步地添加一个任务到Queue,尽可能地使用dispatch_async或dispatch_async_f函数异步地调度任务。因为添加任务到Queue中时,无法确定这些代码什么时候能够执行。因此异步地添加block或函数,可以让你立即调度这些代码的执行,然后调用线程可以继续去做其它事情。特别是应用主线程一定要异步地 dispatch 任务,这样才能及时地响应用户事件
// 2& 同步添加任务
//少数时候你可能希望同步地调度任务,以避免竞争条件或其它同步错误。 使用dispatch_sync和dispatch_sync_f函数同步地添加任务到Queue,这两个函数会阻塞当前调用线程,直到相应任务完成执行。注意:绝对不要在任务中调用 dispatch_sync或dispatch_sync_f函数,并同步调度新任务到当前正在执行的 queue。对于串行queue这一点特别重要,因为这样做肯定会导致死锁;而并发queue也应该避免这样做。
// 调用前,查看下当前线程
NSLog(@&当前调用线程:%@&, [NSThread currentThread]);
// 创建一个串行queue
dispatch_queue_t queueII = dispatch_queue_create(&cn.itcast.queue&, NULL);
dispatch_async(queueII, ^{
NSLog(@&a&&开启了一个异步任务,当前线程:%@&, [NSThread currentThread]);
dispatch_sync(queueII, ^{
NSLog(@&b&&开启了一个同步任务,当前线程:%@&, [NSThread currentThread]);
// 销毁队列
//dispatch_release(queueII);
#pragma mark - 2.并发地执行循环迭代
// 如果你使用循环执行固定次数的迭代, 并发dispatch queue可能会提高性能。
// 例如下面的for循环:
int count = 10;
for (i = 0; i & i++) {
printf(&%d&,i);
// 1& 如果每次迭代执行的任务与其它迭代独立无关,而且循环迭代执行顺序也无关紧要的话,你可以调用dispatch_apply或dispatch_apply_f函数来替换循环。这两个函数为每次循环迭代将指定的block或函数提交到queue。当dispatch到并发 queue时,就有可能同时执行多个循环迭代。用dispatch_apply或dispatch_apply_f时你可以指定串行或并发 queue。并发queue允许同时执行多个循环迭代,而串行queue就没太大必要使用了。
// 下面代码使用dispatch_apply替换了for循环,你传递的block必须包含一个size_t类型的参数,用来标识当前循环迭代。第一次迭代这个参数值为0,最后一次值为count - 1
// 获得全局并发queue
dispatch_queue_t queueIII = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
size_t count1 = 10;
dispatch_apply(count1, queueIII, ^(size_t i) {
printf(&&&&&%zd &, i);
// 可以看出,这些迭代是并发执行的
//和普通for循环一样,dispatch_apply和dispatch_apply_f函数也是在所有迭代完成之后才会返回,因此这两个函数会阻塞当前线程,主线程中调用这两个函数必须小心,可能会阻止事件处理循环并无法响应用户事件。所以如果循环代码需要一定的时间执行,可以考虑在另一个线程中调用这两个函数。如果你传递的参数是串行queue,而且正是执行当前代码的queue,就会产生死锁。
// 销毁队列
//dispatch_release(queue);
#pragma mark - 3.在主线程中执行任务
//1& GCD提供一个特殊的dispatch queue,可以在应用的主线程中执行任务。只要应用主线程设置了run loop(由CFRunLoopRef类型或NSRunLoop对象管理),就会自动创建这个queue,并且最后会自动销毁。非Cocoa应用如果不显式地设置run loop, 就必须显式地调用dispatch_main函数来显式地激活这个dispatch queue,否则虽然你可以添加任务到queue,但任务永远不会被执行。
//2& 调用dispatch_get_main_queue函数获得应用主线程的dispatch queue,添加到这个queue的任务由主线程串行化执行
//3& 代码实现,比如异步下载图片后,回到主线程显示图片
#pragma mark -?异步下载图片?
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [NSURL URLWithString:@&/2014th7cj/d/file/p//xwm5aoyxhle.jpg&];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]]; // 回到主线程显示图片
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image =
#pragma mark -?Dispatch Group的使用?
// 假设有这样一个需求:从网络上下载两张不同的图片,然后显示到不同的UIImageView上去,一般可以这样实现
[self downloadImages1];
// 虽然这种方案可以解决问题,但其实两张图片的下载过程并不需要按顺序执行,并发执行它们可以提高执行速度。有个注意点就是必须等两张图片都下载完毕后才能回到主线程显示图片。Dispatch Group能够在这种情况下帮我们提升性能。下面先看看Dispatch Group的用处:我们可以使用dispatch_group_async函数将多个任务关联到一个Dispatch Group和相应的queue中,group会并发地同时执行这些任务。而且Dispatch Group可以用来阻塞一个线程, 直到group关联的所有的任务完成执行。有时候你必须等待任务完成的结果,然后才能继续后面的处理。
[self downloadImages2];
- (UIImage *)imageWitchURLString:(NSString *)urlString{
NSURL *url =[NSURL URLWithString:urlString];
NSData *data =[NSData dataWithContentsOfURL:url];
return [UIImage imageWithData:data];
- (void)downloadImages1{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 下载第一张图片
NSString *url1
&/2014th7cj/d/file/p//xwm5aoyxhle.jpg&;
UIImage *image1 =[self imageWitchURLString:url1];
// 下载第二张图片
NSString *url2
&/2014th7cj/d/file/p//xwm5aoyxhle.jpg&;
UIImage *image2 =[self imageWitchURLString:url2];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@&1&&&%@/n2&&&%@&,image1,image2);
- (void)downloadImages2{
dispatch_queue_t queue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
// 创建一个组
dispatch_group_t group =dispatch_group_create();
__block UIImage *image1 =
__block UIImage *image2 =
// 关联一个任务到group
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0 ), ^{
NSString *url1
image1 =[self imageWitchURLString:url1];
// 关联第二个任务到group
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *url2
image2 =[self imageWitchURLString:url2];
// 等待组中的任务执行完毕,回到主线程执行block回调
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@&*1&&&%@/n*2&&&%@&,image1,image2);
// 千万不要在异步线程中自动释放UIImage,因为当异步线程结束,异步线程的自动释放池也会被销毁,那么UIImage也会被销毁
四、不同种多线程之间的优缺点
① NSThread优点:NSThread 比其他两个轻量级缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销② NSOperation优点:不需要关心线程管理, 数据同步的事情,可以把精力放在自己需要执行的操作上。Cocoa operation相关的类是NSOperation, NSOperationQueue.NSOperation是个抽象类,使用它必须用它的子类,可以实现它或者使用它定义好的两个子类: NSInvocationOperation和NSBlockOperation.创建NSOperation子类的对象,把对象添加到NSOperationQueue队列里执行。3) GCD
Grand Central dispatch(GCD)是Apple开发的一个多核编程的解决方案。在iOS4.0开始之后才能使用。GCD是一个替代NSThread,NSOperationQueue,NSInvocationOperation等技术的很高效强大的技术。简单点说,GCD就是官方出的一种性能更加好的多线程方法罢了。
以上是的内容,更多
的内容,请您使用右上方搜索功能获取相关信息。
若你要投稿、删除文章请联系邮箱:zixun-group@service.aliyun.com,工作人员会在五个工作日内给你回复。
云服务器 ECS
可弹性伸缩、安全稳定、简单易用
&40.8元/月起
预测未发生的攻击
&24元/月起
为您提供0门槛上云实践机会
你可能还喜欢
你可能感兴趣
阿里云教程中心为您免费提供
几个有关iOS的几个常见问题-----多线程(NSthread、NSOperation、GC相关信息,包括
的信息,所有几个有关iOS的几个常见问题-----多线程(NSthread、NSOperation、GC相关内容均不代表阿里云的意见!投稿删除文章请联系邮箱:zixun-group@service.aliyun.com,工作人员会在五个工作日内答复
售前咨询热线
支持与服务
资源和社区
关注阿里云
InternationalC# C/S 多线程的执行问题
[问题点数:50分,结帖人qq]
C# C/S 多线程的执行问题
[问题点数:50分,结帖人qq]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
2015年2月 总版技术专家分月排行榜第二
2015年2月 Web 开发大版内专家分月排行榜第一2015年1月 Web 开发大版内专家分月排行榜第一2014年12月 Web 开发大版内专家分月排行榜第一2014年11月 Web 开发大版内专家分月排行榜第一2014年10月 Web 开发大版内专家分月排行榜第一
2013年10月 Web 开发大版内专家分月排行榜第三
2017年2月 总版技术专家分月排行榜第三
2018年1月 .NET技术大版内专家分月排行榜第一2017年5月 .NET技术大版内专家分月排行榜第一2017年4月 .NET技术大版内专家分月排行榜第一2017年3月 .NET技术大版内专家分月排行榜第一2017年2月 .NET技术大版内专家分月排行榜第一2016年10月 .NET技术大版内专家分月排行榜第一2016年8月 .NET技术大版内专家分月排行榜第一2016年7月 .NET技术大版内专家分月排行榜第一
2018年2月 .NET技术大版内专家分月排行榜第一
2018年1月 .NET技术大版内专家分月排行榜第二
匿名用户不能发表回复!|多线程 - 廖雪峰的官方网站
多任务可以由多进程完成,也可以由一个进程内的多线程完成。
我们前面提到了进程是由若干线程组成的,一个进程至少有一个线程。
由于线程是操作系统直接支持的执行单元,因此,高级语言通常都内置多线程的支持,Python也不例外,并且,Python的线程是真正的Posix Thread,而不是模拟出来的线程。
Python的标准库提供了两个模块:thread和threading,thread是低级模块,threading是高级模块,对thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。
启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行:
import time, threading
# 新线程执行的代码:
def loop():
print 'thread %s is running...' % threading.current_thread().name
while n & 5:
print 'thread %s &&& %s' % (threading.current_thread().name, n)
time.sleep(1)
print 'thread %s ended.' % threading.current_thread().name
print 'thread %s is running...' % threading.current_thread().name
t = threading.Thread(target=loop, name='LoopThread')
print 'thread %s ended.' % threading.current_thread().name
执行结果如下:
thread MainThread is running...
thread LoopThread is running...
thread LoopThread &&& 1
thread LoopThread &&& 2
thread LoopThread &&& 3
thread LoopThread &&& 4
thread LoopThread &&& 5
thread LoopThread ended.
thread MainThread ended.
由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,Python的threading模块有个current_thread()函数,它永远返回当前线程的实例。主线程实例的名字叫MainThread,子线程的名字在创建时指定,我们用LoopThread命名子线程。名字仅仅在打印时用来显示,完全没有其他意义,如果不起名字Python就自动给线程命名为Thread-1,Thread-2……
多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。
来看看多个线程同时操作一个变量怎么把内容给改乱了:
import time, threading
# 假定这是你的银行存款:
balance = 0
def change_it(n):
# 先存后取,结果应该为0:
global balance
balance = balance + n
balance = balance - n
def run_thread(n):
for i in range(100000):
change_it(n)
t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
print balance
我们定义了一个共享变量balance,初始值为0,并且启动两个线程,先存后取,理论上结果应该为0,但是,由于线程的调度是由操作系统决定的,当t1、t2交替执行时,只要循环次数足够多,balance的结果就不一定是0了。
原因是因为高级语言的一条语句在CPU执行时是若干条语句,即使一个简单的计算:
balance = balance + n
也分两步:
计算balance + n,存入临时变量中;
将临时变量的值赋给balance。
也就是可以看成:
x = balance + n
balance = x
由于x是局部变量,两个线程各自都有自己的x,当代码正常执行时:
初始值 balance = 0
t1: x1 = balance + 5 # x1 = 0 + 5 = 5
t1: balance = x1
# balance = 5
t1: x1 = balance - 5 # x1 = 5 - 5 = 0
t1: balance = x1
# balance = 0
t2: x2 = balance + 8 # x2 = 0 + 8 = 8
t2: balance = x2
# balance = 8
t2: x2 = balance - 8 # x2 = 8 - 8 = 0
t2: balance = x2
# balance = 0
结果 balance = 0
但是t1和t2是交替运行的,如果操作系统以下面的顺序执行t1、t2:
初始值 balance = 0
t1: x1 = balance + 5
# x1 = 0 + 5 = 5
t2: x2 = balance + 8
# x2 = 0 + 8 = 8
t2: balance = x2
# balance = 8
t1: balance = x1
# balance = 5
t1: x1 = balance - 5
# x1 = 5 - 5 = 0
t1: balance = x1
# balance = 0
t2: x2 = balance - 5
# x2 = 0 - 5 = -5
t2: balance = x2
# balance = -5
结果 balance = -5
究其原因,是因为修改balance需要多条语句,而执行这几条语句时,线程可能中断,从而导致多个线程把同一个对象的内容改乱了。
两个线程同时一存一取,就可能导致余额不对,你肯定不希望你的银行存款莫名其妙地变成了负数,所以,我们必须确保一个线程在修改balance的时候,别的线程一定不能改。
如果我们要确保balance计算正确,就要给change_it()上一把锁,当某个线程开始执行change_it()时,我们说,该线程因为获得了锁,因此其他线程不能同时执行change_it(),只能等待,直到锁被释放后,获得该锁以后才能改。由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的冲突。创建一个锁就是通过threading.Lock()来实现:
balance = 0
lock = threading.Lock()
def run_thread(n):
for i in range(100000):
# 先要获取锁:
lock.acquire()
# 放心地改吧:
change_it(n)
# 改完了一定要释放锁:
lock.release()
当多个线程同时执行lock.acquire()时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。
获得锁的线程用完后一定要释放锁,否则那些苦苦等待锁的线程将永远等待下去,成为死线程。所以我们用try...finally来确保锁一定会被释放。
锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。
如果你不幸拥有一个多核CPU,你肯定在想,多核应该可以同时执行多个线程。
如果写一个死循环的话,会出现什么情况呢?
打开Mac OS X的Activity Monitor,或者Windows的Task Manager,都可以监控某个进程的CPU使用率。
我们可以监控到一个死循环线程会100%占用一个CPU。
如果有两个死循环线程,在多核CPU中,可以监控到会占用200%的CPU,也就是占用两个CPU核心。
要想把N核CPU的核心全部跑满,就必须启动N个死循环线程。
试试用Python写个死循环:
import threading, multiprocessing
def loop():
while True:
for i in range(multiprocessing.cpu_count()):
t = threading.Thread(target=loop)
启动与CPU核心数量相同的N个线程,在4核CPU上可以监控到CPU占用率仅有160%,也就是使用不到两核。
即使启动100个线程,使用率也就170%左右,仍然不到两核。
但是用C、C++或Java来改写相同的死循环,直接可以把全部核心跑满,4核就跑到400%,8核就跑到800%,为什么Python不行呢?
因为Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。
GIL是Python解释器设计的历史遗留问题,通常我们用的解释器是官方实现的CPython,要真正利用多核,除非重写一个不带GIL的解释器。
所以,在Python中,可以使用多线程,但不要指望能有效利用多核。如果一定要通过多线程利用多核,那只能通过C扩展来实现,不过这样就失去了Python简单易用的特点。
不过,也不用过于担心,Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。
多线程编程,模型复杂,容易发生冲突,必须用锁加以隔离,同时,又要小心死锁的发生。
Python解释器由于设计时有GIL全局锁,导致了多线程无法利用多核。多线程的并发在Python中就是一个美丽的梦。
Make a comment
Sign in to make a comment
You can sign in directly without register:
You need authorize to allow connect to your social passport for the first time.
WARNING: You are using an old browser that does not support HTML5.
Please choose a modern browser ( /
/ ) to get a good experience.

我要回帖

更多关于 for循环多线程 的文章

 

随机推荐