废话不多说,直接上干货。先熟悉一下基本知识,然后讲一下常用的两种,NSOperation和GCD。
一、基础概念
进程:
狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
进程的概念主要有两点:第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。
进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。
 
线程:
    一个CPU一次只能执行一个命令,不能执行某处分开的并列的两个命令,因此通过CPU执行的CPU命令就好比一条无分叉的大道,其执行不对出现分歧 。
    这里所说的“一个CPU执行的CPU命令列为一条无分叉路径”,即为“线程”。
    这种无分叉路径不只1条,存在有多条时即为“多线程”。
    基本上1个CPU核一次能够执行的CPU命令始终为1,那么怎么才能在多条路径中执行CPU命令列呢?
    OS X和iOS的核心XNU内核在发生操作系统事件时(如每隔一定时间,唤起系统调用等情况)会切换执行路径。执行中路径的状态,例如CPU的寄存器等信息保存到各自路径专用的内存块中,从切换目标路径专用的内存块中,复原CPU寄存器等信息,继续执行切换路径的CPU命令列。这被称为“上下文切换”。
    由于使用多线程的程序可以在某个线程和其他线程之间反复多次进行上下文切换,因此看上去就好像1个CPU核能够并列的执行多个线程一样。而且在具有多个CPU核的情况下,就不是“看上去像了”,而是真的提供了多个CPU核并行执行多个线程的技术了。
    这种利用多线程编程的技术就被称为“多线程编程”。
 
同步:
    就是在发出一个调用时,在没有得到结果之前,该调用就不反回。但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果。
 
异步:
    而异步则是相反,调用在发出之后,这个调用就直接返回了,所以就没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出之后,被调用者通过“状态”、“通知”、“回调”三种途径通知调用者。
 
串行:一个线程按顺序执行
并发:由于使用多线程的程序可以在某个线程和其他线程之间反复多次进行上下文切换,因此看上去就好像1个CPU核能够并列的执行多个线程一样。(同时存在)
并行:多个CPU核并行执行多个线程(同时执行)
 
注意:如果某个系统支持两个或者多个动作(Action)同时存在,那么这个系统就是一个并发系统。如果某个系统支持两个或者多个动作同时执行,那么这个系统就是一个并行系统。并发系统与并行系统这两个定义之间的关键差异在于“存在”这个词。“并行”概念是“并发”概念的一个子集
 
再简单一点:
并发:交替做不同事的能力
并行:同时做不同事的能力
专业解释:
并发:不同代码块交替执行的性能
并行:不同代码块同时执行的性能
 
整理一下基本概念:
1 同步异步针对时间,同步会阻塞当前线程,任务完成同步函数才会返回,线程继续执行,异步不会阻塞当前线程,异步函数马上返回,线程继续执行
2 串行,并行针对空间,串行在同一线程顺序执行。并行在不同线程执行
               并发与并行的区别?
 
二、使用GCD进行多线程编程
2.1 什么是GCD:Grand Central Dispatch(GCD)是异步执行任务的技术之一,用我们难以置信的非常简洁的记述方法,实现了极为复杂繁琐的多线程编程。
2.2 GCD的API
  2.2.1 Dispatch Queue:如其名称所示,是执行处理的等待队列。
          开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中。

dispatch_async(queue, ^{
NSLog(@"想执行的任务";
});

  编程人员在Block语法中记述想执行的处理并将其追加到Dispatch Queue中。Dispatch Queue按照追加顺序(先进先出 FIFO,First-In-First-Out)执行处理。

  DIspatch Queue有两种,一种是Serial Dispatch Queue(串行队列),一种是Concurrent Dispatch Queue(并行队列)。
串行队列(Serial Dispatch Queue):

 // 自定义串行队列,将任务ABC加到串行队列中,顺序执行
dispatch_queue_t customSerialQueue = dispatch_queue_create("test.wangdachui.MyCustomQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(customSerialQueue, ^{
NSLog(@"customSerialQueue-A");
});
dispatch_async(customSerialQueue, ^{
NSLog(@"customSerialQueue-B");
});
dispatch_async(customSerialQueue, ^{
NSLog(@"customSerialQueue-C");
});
// 由于上面是异步执行操作,所以很难知道下面的打印和上面异步操作中的打印谁先谁后
NSLog(@"customSerialQueue-D"); //多个串行队列并行执行,系统对于一个serialQueue就只生成并使用一个线程。如果生成2000个serialQueue,那么就生成2000个线程
dispatch_queue_t customSerialQueue1 = dispatch_queue_create("test.wangdachui.MyCustomQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t customSerialQueue2 = dispatch_queue_create("test.wangdachui.MyCustomQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t customSerialQueue3 = dispatch_queue_create("test.wangdachui.MyCustomQueue", DISPATCH_QUEUE_SERIAL); dispatch_async(customSerialQueue1, ^{
NSLog(@"customSerialQueue1-A");
});
dispatch_async(customSerialQueue2, ^{
NSLog(@"customSerialQueue2-B");
});
dispatch_async(customSerialQueue3, ^{
NSLog(@"customSerialQueue3-C");
});
//注意:过多使用多线程,就会消耗大量内存问题,引起大量的上下文切换,大幅度降低系统的响应性能

并行队列(Concurrent Dispatch Queue):

iOS和OS X的核心--XNU内核决定应当使用的线程数,并只生成所需的线程执行处理。另外,当处理结束,应当执行的处理数减少时,XNU内核会结束不再需要的线程。XNU内核仅使用Concurrent Dispatch Queue便可以完美地管理并行执行多个处理的线程。

假设准备4个Concurrent Dispatch Queue 用线程。首先blk0在线程0中开始执行,接着blk1在线程1中、blk2在线程2中、blk3在线程3中开始执行。线程0中blk0执行结束后开始执行blk4,由于线程1中blk1的执行没有结束,因此线程2中blk2执行结束后开始执行blk5,就这样循环往复。

像这样在Concurrent Dispatch Queue中执行处理时,执行顺序会根据处理内容和系统状态发生改变。

为了说明线程分配原理,这里假设线程数为4,实测iOS11线程数可达20个,所以想测试的同学,在并发队列中必须追加20个以上的任务

对于Concurrent Dispatch Queue来说,不管生成多少,由于XNU内核只使用有效管理的线程,因此不会发生串行队列的那些问题(过多使用多线程,降低系统的响应性能)

 // 自定义并行队列,将任务1234567加到串行队列中
dispatch_queue_t customConcurrentQueue = dispatch_queue_create("test.wangdachui.MyCustomQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(customConcurrentQueue, ^{
NSLog(@"blk0");
NSLog(@"当前线程:%@",[NSThread currentThread]);
});
dispatch_async(customConcurrentQueue, ^{
NSLog(@"blk1");
NSLog(@"当前线程:%@",[NSThread currentThread]);
});
dispatch_async(customConcurrentQueue, ^{
NSLog(@"blk2");
NSLog(@"当前线程:%@",[NSThread currentThread]);
});
dispatch_async(customConcurrentQueue, ^{
NSLog(@"blk3");
NSLog(@"当前线程:%@",[NSThread currentThread]);
});
dispatch_async(customConcurrentQueue, ^{
NSLog(@"blk4");
NSLog(@"当前线程:%@",[NSThread currentThread]);
});
dispatch_async(customConcurrentQueue, ^{
NSLog(@"blk5");
NSLog(@"当前线程:%@",[NSThread currentThread]);
});
dispatch_async(customConcurrentQueue, ^{
NSLog(@"blk6");
NSLog(@"当前线程:%@",[NSThread currentThread]);
});

2.2.2 Dispatch Group:在使用Concurrent Dispatch Queue或同时使用多个Dispatch Queue时,想要在Dispatch Queue中的处理全部结束后再执行其他处理。可以使用Dispatch Group。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, );
dispatch_group_t group = dispatch_group_create();
// 把 queue 加入到 group
dispatch_group_async(group, queue, ^{
// 一些异步操作任务
sleep();
NSLog(@"任务GroupA\n当前线程:%@",[NSThread currentThread]);
});
// code 你可以在这里写代码做一些不必等待 group 内任务的操作
NSLog(@"任务GroupB\n当前线程:%@",[NSThread currentThread]);
// 当你在 group 的任务没有完成的情况下不能做更多的事时,阻塞当前线程等待 group 完工
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"任务GroupC\n当前线程:%@",[NSThread currentThread]);
dispatch_group_notify(group, dispatch_get_main_queue(), ^(){
// 从主线程上执行 UI 界面更新
NSLog(@"任务GroupD\n当前线程:%@",[NSThread currentThread]);
});

2.2.3 dispatch_barrier_async:

在访问数据库或文件时,为了高效地进行访问,读取处理追加到Concurrent Dispatch Queue中,写入处理在任一读取处理没有执行的状态下,追加到Serial Dispatch Queue中即可(在写入处理结束之前,读取处理不可执行)

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, );
dispatch_async(queue, ^{
NSLog(@"blk0_for_reading");
});
dispatch_async(queue, ^{
NSLog(@"blk1_for_reading");
});
dispatch_async(queue, ^{
NSLog(@"blk2_for_writing");
});
dispatch_async(queue, ^{
NSLog(@"blk3_for_reading");
});
dispatch_async(queue, ^{
NSLog(@"blk4_for_reading");
});
/*如上,如果简单地在dispatch_async函数中加入写入处理,那么根据Concurrent Dispatch Queue的性质,就有可能在追加到写入处理前面的处理中读取到与期待不符的数据,还可能因非法访问导致应用程序异常结束。因此我们要使用dispatch_barrier_async函数。dispatch_barrier_async函数会等待追加到Concurrent Dispatch Queue上的并行执行的处理全部结束之后,再将指定的处理追加到该Concurrent Dispatch Queue中。然后在由dispatch_barrier_async函数追加的处理执行完毕后,Concurrent Dispatch Queue才恢复为一般的动作,追加到该Concurrent Dispatch Queue的处理又开始执行。*/
dispatch_async(queue, ^{
NSLog(@"blk0_for_reading");
});
dispatch_async(queue, ^{
NSLog(@"blk1_for_reading");
});
dispatch_barrier_async(queue, ^{
NSLog(@"blk2_for_writing");
});
dispatch_async(queue, ^{
NSLog(@"blk3_for_reading");
});
dispatch_async(queue, ^{
NSLog(@"blk4_for_reading");
});

2.2.4 Dispatch Semaphore:信号量,关于信号量可以看我另外一篇帖子:iOS 信号量

// 创建信号量,并且设置值为10
dispatch_semaphore_t semaphore = dispatch_semaphore_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, );
for (int i = ; i < ; i++)
{ // 由于是异步执行的,所以每次循环Block里面的dispatch_semaphore_signal根本还没有执行就会执行dispatch_semaphore_wait,从而semaphore-1.当循环10次后,semaphore等于0,则会阻塞线程,直到执行了Block的dispatch_semaphore_signal 才会继续执行
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
NSLog(@"信号量-index=%i",i);
NSLog(@"信号量当前线程:%@",[NSThread currentThread]);
sleep();
// 每次发送信号则semaphore会+1,
dispatch_semaphore_signal(semaphore);
});
}

2.2.5 dispatch_apply:该函数按指定的次数将指定的Block追加到指定的Queue中,并等待全部处理执行结束

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, );
dispatch_apply(, queue, ^(size_t index) {
NSLog(@"%zu",index);
});
NSLog(@"done");
/*打印结果
2017-09-20 10:49:29.760594+0800 Multithreading[11622:459025] 2
2017-09-20 10:49:29.760594+0800 Multithreading[11622:458947] 3
2017-09-20 10:49:29.760594+0800 Multithreading[11622:459027] 0
2017-09-20 10:49:29.760594+0800 Multithreading[11622:459026] 1
2017-09-20 10:49:29.760609+0800 Multithreading[11622:459077] 4
2017-09-20 10:49:29.760634+0800 Multithreading[11622:459078] 5
2017-09-20 10:49:29.760650+0800 Multithreading[11622:459079] 6
2017-09-20 10:49:29.760653+0800 Multithreading[11622:459080] 7
2017-09-20 10:49:29.760737+0800 Multithreading[11622:458947] 8
2017-09-20 10:49:29.760738+0800 Multithreading[11622:459025] 9
2017-09-20 10:49:29.761195+0800 Multithreading[11622:458947] done
*/

2.3 关于同步异步,串行并行的思考,看看打印结果,你就能悟真谛了。看的时候可以把测试代码拷贝到自己的项目中,自己思考一下,再回头看看打印结果。这样效果更好。大家不要嫌我写的烦,要好好听课。

//关于同步异步,串行,并行的思考
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, );
dispatch_queue_t serialQueue = dispatch_queue_create("test.Lision.MyCustomQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t concurrentQueue = dispatch_queue_create("test.Lision.MyCustomQueue", DISPATCH_QUEUE_CONCURRENT); [self dispatchAsyncWith:mainQueue];
/* 打印结果
2017-09-20 19:25:40.815678+0800 Multithreading[34153:2558588] 任务C
当前线程:<NSThread: 0x604000077440>{number = 1, name = main}
2017-09-20 19:25:42.843920+0800 Multithreading[34153:2558588] 任务A
当前线程:<NSThread: 0x604000077440>{number = 1, name = main}
2017-09-20 19:25:44.845176+0800 Multithreading[34153:2558588] 任务B
当前线程:<NSThread: 0x604000077440>{number = 1, name = main}
*/ //[self dispatchAsyncWith:globalQueue];
/* 打印结果:A/B的顺序随机
2017-09-20 19:28:26.395395+0800 Multithreading[34347:2580292] 任务C
当前线程:<NSThread: 0x60400006dc80>{number = 1, name = main}
2017-09-20 19:28:28.397384+0800 Multithreading[34347:2580443] 任务B
当前线程:<NSThread: 0x604000275ac0>{number = 3, name = (null)}
2017-09-20 19:28:28.397384+0800 Multithreading[34347:2580446] 任务A
当前线程:<NSThread: 0x60c00007c680>{number = 4, name = (null)}
*/ //[self dispatchAsyncWith:serialQueue];
/* 打印结果:串行队列生成一个新线程,AB顺序执行
2017-09-20 19:29:45.542392+0800 Multithreading[34394:2585219] 任务C
当前线程:<NSThread: 0x60000007f880>{number = 1, name = main}
2017-09-20 19:29:47.547666+0800 Multithreading[34394:2585681] 任务A
当前线程:<NSThread: 0x604000277540>{number = 3, name = (null)}
2017-09-20 19:29:49.550736+0800 Multithreading[34394:2585681] 任务B
当前线程:<NSThread: 0x604000277540>{number = 3, name = (null)}
*/ //[self dispatchAsyncWith:concurrentQueue];
/* 打印结果 A/B的顺序随机,AB并行执行,生成两个线程,最多生成几个线程由系统决定
2017-09-20 19:30:56.998360+0800 Multithreading[34446:2593417] 任务C
当前线程:<NSThread: 0x60400006ba40>{number = 1, name = main}
2017-09-20 19:30:59.001208+0800 Multithreading[34446:2593777] 任务B
当前线程:<NSThread: 0x608000275ac0>{number = 3, name = (null)}
2017-09-20 19:30:59.001206+0800 Multithreading[34446:2593775] 任务A
当前线程:<NSThread: 0x60400007ae40>{number = 4, name = (null)}
*/ //[self dispatchSyncWith:mainQueue];//死锁
/*
死锁原因:我们要在主线程同步执行任务A,但是同步执行任务A也算一个任务,我们称呼它为W。mainQueue是顺序执行的,当前正在执行的任务是W,W的内容是要执行A,所以把A加到mainQueue的尾部等待执行。A要执行,必须等W完成,W要完成,必须要执行A,相互等待,进入死锁。
所以,同步的时候,不能将任务添加到当前线程的串行Queue中
*/
//[self dispatchSyncWith:globalQueue];
/* 打印结果 同步阻塞,顺序执行ABC
2017-09-20 19:59:25.961727+0800 Multithreading[34773:2655128] 任务A
当前线程:<NSThread: 0x60c000261bc0>{number = 1, name = main}
2017-09-20 19:59:27.962571+0800 Multithreading[34773:2655128] 任务B
当前线程:<NSThread: 0x60c000261bc0>{number = 1, name = main}
2017-09-20 19:59:27.962824+0800 Multithreading[34773:2655128] 任务C
当前线程:<NSThread: 0x60c000261bc0>{number = 1, name = main}
*/
// [self dispatchSyncWith:serialQueue];
/*
自己猜
*/
// [self dispatchSyncWith:concurrentQueue];
/*
自己猜
*/
} //异步调用各种Queue
- (void)dispatchAsyncWith:(dispatch_queue_t)queue {
dispatch_async(queue, ^{
sleep();
NSLog(@"任务A\n当前线程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
sleep();
NSLog(@"任务B\n当前线程:%@",[NSThread currentThread]);
});
NSLog(@"任务C\n当前线程:%@",[NSThread currentThread]);
} //同步调用各种Queue
- (void)dispatchSyncWith:(dispatch_queue_t)queue {
dispatch_sync(queue, ^{
sleep();
NSLog(@"任务A\n当前线程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
sleep();
NSLog(@"任务B\n当前线程:%@",[NSThread currentThread]);
});
NSLog(@"任务C\n当前线程:%@",[NSThread currentThread]);
}

留了两个没有给结果,很简单的,经过上面的学习,结果是不是很简单。帖子准备了好几天,查阅了很多资料。关于使用NSOperation进行多线程编程,看我这篇帖子:iOS多线程--NSOperation

demo下载:https://github.com/wangdachui/multithreading.git

iOS多线程编程的更多相关文章

  1. iOS多线程编程指南

    iOS多线程编程指南(拓展篇)(1) 一.Cocoa 在Cocoa上面使用多线程的指南包括以下这些: (1)不可改变的对象一般是线程安全的.一旦你创建了它们,你可以把这些对象在线程间安全的传递.另一方 ...

  2. iOS多线程编程原理及实践

    摘要:iOS开发中,开发者不仅要做好iOS的内存管理,而且如果你的iOS涉及多线程,那你也必须了解iOS编程中对多线程的限制,iOS主线程的堆栈大小为1M,其它线程均为512KB,且这个限制开发者是无 ...

  3. IOS高级编程之三:IOS 多线程编程

    多线程的概念在各个操作系统上都会接触到,windows.Linux.mac os等等这些常用的操作系统,都支持多线程的概念. 当然ios中也不例外,但是线程的运行节点可能是我们平常不太注意的. 例如: ...

  4. iOS多线程编程Part 1/3 - NSThread & Run Loop

    前言 多线程的价值无需赘述,对于App性能和用户体验都有着至关重要的意义,在iOS开发中,Apple提供了不同的技术支持多线程编程,除了跨平台的pthread之外,还提供了NSThread.NSOpe ...

  5. iOS多线程编程指南(一)关于多线程编程(转)

    原文:http://www.dreamingwish.com/article/ios-multi-threaded-programming-a-multi-threaded-programming.h ...

  6. iOS多线程编程技术之NSThread、Cocoa NSOperation、GCD

    原文出处: 容芳志的博客 简介iOS有三种多线程编程的技术,分别是:(一)NSThread(二)Cocoa NSOperation(三)GCD(全称:Grand Central Dispatch) 这 ...

  7. iOS多线程编程(四)------ GCD(Grand Central Dispatch)

    一.简单介绍 是基于C语言开发的一套多线程开发机制.也是眼下苹果官方推荐的多线程开发方法.用起来也最简单.仅仅是它基于C语言开发,并不像NSOperation是面向对象的开发.而是全然面向过程的.假设 ...

  8. iOS多线程编程的知识梳理

    多线程编程也称之为并发编程,由于其作用大,有比较多的理论知识,因此在面试中也是受到面试官的青睐.在日常项目开发中,至少网络请求上是需要使用到多线程知识的,虽然使用第三方的框架比如AFNetworkin ...

  9. iOS多线程编程Part 2/3 - NSOperation

    多线程编程Part 1介绍了NSThread以及NSRunLoop,这篇Blog介绍另一种并发编程技术:NSOPeration. NSOperation & NSOperationQueue ...

随机推荐

  1. 设计模式(8)--Decorator--装饰器模式--结构型

    1.模式定义: 装饰模式又名包装(Wrapper)模式.装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案. 2.模式特点:    装饰模式能够实现动态的为对象添加功能,是从一个对象 ...

  2. linux 下 Fatal error: Class ‘mysqli’ not found in

    先试用这种方法 http://blog.csdn.net/u010429424/article/details/43063211 我不知道自己安装的php 没他们路径,所以用了以下这种方法处理,并且不 ...

  3. fodera20安装后的配置

    最近安装了Fedora 20 64bit,以下是一些优化配置,使之更适合国人使用. 1,安装gnome-tweak-tool设置工具 Fedora 19自带的系统设置工具十分简单,一些重要的地方都不能 ...

  4. .net 正则获取url参数

    public static string GetParams(string paramName) { var url = "http://fdsfs.com/Home/Index?corp= ...

  5. C#使用Xamarin开发可移植移动应用进阶篇(7.使用布局渲染器,修改默认布局),附源码

    前言 系列目录 C#使用Xamarin开发可移植移动应用目录 源码地址:https://github.com/l2999019/DemoApp 可以Star一下,随意 - - 说点什么.. 本篇..基 ...

  6. 总结切面编程AOP的注解式开发和XML式开发

    有段日子没有总结东西了,因为最近确实有点忙,一直在忙于hadoop集群的搭建,磕磕碰碰现在勉强算是能呼吸了,因为这都是在自己的PC上,资源确实有点紧张(搭建过程后期奉上),今天难得大家都有空(哈哈哈~ ...

  7. We’re Just Beginning

    "We are reading the first verse of the first chapter of a book whose pages are infinite…" ...

  8. Android studio一些常见技巧(不断更新)

    一.Android studio取消默认每次打开时打开最后一个项目 二.as添加jar包 新建一个libs目录,在java下 进行手动gragle同步或者如下图 三.代码边上的小图标 1.布局文件中存 ...

  9. 两台主机之间单向Ping不通的问题

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px ".PingFang SC"; color: #454545 } p.p2 ...

  10. Spark Submit 脚本

    当我们需要命令行传递参数时候,将--class 写在前面,然后是jar 最后是参数 spark-submit --master yarn --num-executors 3 --executor-me ...