iOS多线程(上)——GCD详解(上)
GCD(Grand central Dispatch)是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。下面我讲讲述关于GCD的点,通篇读完大约10-20分钟。
一、为什么要用GCD?
GCD是iOS线程的一种,也是被经常使用的一种方式。GCD也有很多的好处:
(1)GCD可用于多核的并行运算;
(2)GCD会自动利用更多的CPU内核;
(3)GCD会自动管理线程的生命周期;
(4)程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
二、GCD任务和队列
先了解GCD中两个核心概念:任务和队列。
1.任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在GCD中是放在block中的。执行任务有两种方式:同步执行(sync)和异步执行(async)。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
1).同步执行(sync):
同步添加到指定的队列中,在添加的任务执行结束之前,会一直等待,知道队列里面的任务完成之后再继续执行。
只能在当前线程中执行任务,不具备开启新线程的能力。
2).异步执行(async):
异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
可以在新的线程中执行任务,具备开启新线程的能力。
注意: 异步执行(async) 虽然具有开启新线程的能力,但是并不一定开启新线程。这跟任务所指定的队列类型有关(下面会讲)。
2.队列:这里的队列指的是执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。
在GCD中有两种队列:串行队列和并发队列。两者都符合FIFO(先进先出)的原则。两者的主要区别是:执行的顺序不同,以及开启线程数不同。
1)串行队列:
每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
2)并发队列:
可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
注意:并发队列 的并发功能只有在异步(dispatch_async)函数下才有效
三、GCD的使用步骤
GCD的使用步骤其实比较简单,只有两步。
1.创建一个队列(串行队列或者并行队列)
2.将任务追加到任务的等待队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)
3.1队列的创建方法/获取方法
可以使用dispatch_queue_create来创建队列,需要传入两个参数,第一个参数表示队列的唯一标识符,用于DEBUG,可为空,Dispatch Queue的名称推荐使用应用程序ID这种逆序全程域名,第二个参数用来表示串行队列还是并发队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并发队列。
下面是创建队列的例子:
// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
对于串行队列,GCD提供了一种特殊的串行队列:主队列(Main Dispatch Queue)。
1)所有放在主队列的任务,都会放到主线程中执行。
2)可使用dispatch_get_main_queue()获得主队列。
// 主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();
对于并发队列,GCD默认提供了全局并发队列(Global Dispatch Queue)
可以使用dispatch_get_global_queue
来获取。需要传入两个参数。第一个参数是队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,可以用0即可。
// 全局并发队列的获取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, );
3.2任务的创建方法
GCD提供了同步执行任务的创建方法dispatch_sync和异步执行任务创建方法dispatch_async。
// 同步执行任务创建方法
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
// 这里放异步执行任务代码
});
虽然使用GCD只需要两步,但是既然我们有两种队列(串行队列/并行队列),两种任务执行方式(同步执行/异步执行)那么我们就有了四种不同的组合方式。这四种不同的方式为:
1.同步执行 + 并发队列
2.异步执行 + 并发队列
3.同步执行 + 串行队列
4.异步执行 + 串行队列
实际上,刚才说了两种特殊队列:全局并发队列、主队列。全局并发队列可以作为普通并发队列来使用,但是主队列因为有点特殊,我们就又多了两种组合方式。我们就有了六种不同的方式了。
5.同步执行 + 主队列
6.异步执行 + 主队列
那么这几种不同组合方式各有什么区别呢,这里为了方便,先上结果,再来讲解。
四、GCD的基本使用
4.1 同步执行+并发队列
在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
(void)syncConcurrent {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"syncConcurrent---begin"); dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_sync(queue, ^{
// 追加任务1
for (int i = ; i < ; ++i) {
[NSThread sleepForTimeInterval:]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
}
}); dispatch_sync(queue, ^{
// 追加任务2
for (int i = ; i < ; ++i) {
[NSThread sleepForTimeInterval:]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
}
}); dispatch_sync(queue, ^{
// 追加任务3
for (int i = ; i < ; ++i) {
[NSThread sleepForTimeInterval:]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
}
}); NSLog(@"syncConcurrent---end");
打印输出结果:
输出结果: -- ::55.095932+ YSC-GCD-demo[:] currentThread---{number = , name = main} -- ::55.096086+ YSC-GCD-demo[:] syncConcurrent---begin -- ::57.097589+ YSC-GCD-demo[:] ---{number = , name = main} -- ::59.099100+ YSC-GCD-demo[:] ---{number = , name = main} -- ::01.099843+ YSC-GCD-demo[:] ---{number = , name = main} -- ::03.101171+ YSC-GCD-demo[:] ---{number = , name = main} -- ::05.101750+ YSC-GCD-demo[:] ---{number = , name = main} -- ::07.102414+ YSC-GCD-demo[:] ---{number = , name = main} -- ::07.102575+ YSC-GCD-demo[:] syncConcurrent---end
从结果可以看到:
(1)所有任务都是在当前线程(主线程)中执行的,没有开启新的线程(同步执行不具备开启新线程的能力)。
(2)所有任务都在打印的syncConcurrent---begin和syncConcurrent---end之间执行的(同步任务需要等待队列的任务执行结束)。
(3)任务按顺序执行的。按顺序执行的原因:虽然并发队列可以开启多个线程,并且同时执行多个任务。但是因为本身不能创建新线程,只有当前线程这一个线程(同步任务不具备开启新线程的能力),所以也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续接着执行下面的操作(同步任务需要等待队列的任务执行结束)。所以任务只能一个接一个按顺序执行,不能同时被执行。
4.2 异步执行+并发队列
可以开启多个线程,任务交替(同时)执行
- (void)asyncConcurrent {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"asyncConcurrent---begin"); dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{
// 追加任务1
for (int i = ; i < ; ++i) {
[NSThread sleepForTimeInterval:]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
}
}); dispatch_async(queue, ^{
// 追加任务2
for (int i = ; i < ; ++i) {
[NSThread sleepForTimeInterval:]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
}
}); dispatch_async(queue, ^{
// 追加任务3
for (int i = ; i < ; ++i) {
[NSThread sleepForTimeInterval:]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
}
}); NSLog(@"asyncConcurrent---end");
}
结果如下:
输出结果: -- ::41.769269+ YSC-GCD-demo[:] currentThread---{number = , name = main} -- ::41.769496+ YSC-GCD-demo[:] asyncConcurrent---begin -- ::41.769725+ YSC-GCD-demo[:] asyncConcurrent---end -- ::43.774442+ YSC-GCD-demo[:] ---{number = , name = (null)} -- ::43.774440+ YSC-GCD-demo[:] ---{number = , name = (null)} -- ::43.774440+ YSC-GCD-demo[:] ---{number = , name = (null)} -- ::45.779286+ YSC-GCD-demo[:] ---{number = , name = (null)} -- ::45.779302+ YSC-GCD-demo[:] ---{number = , name = (null)} -- ::45.779286+ YSC-GCD-demo[:] ---{number = , name = (null)}
在异步执行+并发队列可以看出:
除了当前线程(主线程),系统又开启了3个线程,并且任务是交替/同时执行的。(异步执行具备开启新线程的能力。且并发队列可开启多个线程,同时执行多个任务)。所有任务是在打印的syncConcurrent---begin和syncConcurrent---end之后才执行的。说明当前线程没有等待,而是直接开启了新线程,在新线程中执行任务(异步执行不做等待,可以继续执行任务)。
4.3 同步执行 + 串行队列
不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。
- (void)syncSerial {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"syncSerial---begin"); dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL); dispatch_sync(queue, ^{
// 追加任务1
for (int i = ; i < ; ++i) {
[NSThread sleepForTimeInterval:]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_sync(queue, ^{
// 追加任务2
for (int i = ; i < ; ++i) {
[NSThread sleepForTimeInterval:]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_sync(queue, ^{
// 追加任务3
for (int i = ; i < ; ++i) {
[NSThread sleepForTimeInterval:]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
}
}); NSLog(@"syncSerial---end");
}
输出结果:
-- ::37.876811+ YSC-GCD-demo[:] currentThread---{number = , name = main} -- ::37.876998+ YSC-GCD-demo[:] syncSerial---begin -- ::39.878316+ YSC-GCD-demo[:] ---{number = , name = main} -- ::41.879829+ YSC-GCD-demo[:] ---{number = , name = main} -- ::43.880660+ YSC-GCD-demo[:] ---{number = , name = main} -- ::45.881265+ YSC-GCD-demo[:] ---{number = , name = main} -- ::47.882257+ YSC-GCD-demo[:] ---{number = , name = main} -- ::49.883008+ YSC-GCD-demo[:] ---{number = , name = main} -- ::49.883253+ YSC-GCD-demo[:] syncSerial---end
在同步执行+串行队列可以看出
(1)所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(同步执行不具备开启新线程的能力)。
(2)所有任务都在打印的syncConcurrent---begin和syncConcurrent---end之间执行(同步任务需要等待队列的任务执行结束)。
(3)任务是按顺序执行的(串行队列每次只有一个任务被执行,任务一个接一个按顺序执行)。
4.4 异步执行 + 串行队列
会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务
- (void)asyncSerial {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"asyncSerial---begin"); dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{
// 追加任务1
for (int i = ; i < ; ++i) {
[NSThread sleepForTimeInterval:]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_async(queue, ^{
// 追加任务2
for (int i = ; i < ; ++i) {
[NSThread sleepForTimeInterval:]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_async(queue, ^{
// 追加任务3
for (int i = ; i < ; ++i) {
[NSThread sleepForTimeInterval:]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
}
}); NSLog(@"asyncSerial---end");
}
执行结果如下:
-- ::17.029999+ YSC-GCD-demo[:] currentThread---{number = , name = main} -- ::17.030212+ YSC-GCD-demo[:] asyncSerial---begin -- ::17.030364+ YSC-GCD-demo[:] asyncSerial---end -- ::19.035379+ YSC-GCD-demo[:] ---{number = , name = (null)} -- ::21.037140+ YSC-GCD-demo[:] ---{number = , name = (null)} -- ::23.042220+ YSC-GCD-demo[:] ---{number = , name = (null)} -- ::25.042971+ YSC-GCD-demo[:] ---{number = , name = (null)} -- ::27.047690+ YSC-GCD-demo[:] ---{number = , name = (null)} -- ::29.052327+ YSC-GCD-demo[:] ---{number = , name = (null)}
从执行结果看:
(1)开启了一条新线程(异步执行具备开启新线程的能力,串行队列只开启一个线程)。
(2)所有任务是在打印的syncConcurrent---begin和syncConcurrent---end之后才开始执行的(异步执行不会做任何等待,可以继续执行任务)。
(3)任务是按顺序执行的(串行队列每次只有一个任务被执行,任务一个接一个按顺序执行)。
下边讲讲刚才我们提到过的特殊队列:主队列。
主队列:GCD自带的一种特殊的串行队列
所有放在主队列中的任务,都会放到主线程中执行
可使用dispatch_get_main_queue()获得主队列
我们再来看看主队列的两种组合方式。
4.5 同步执行 + 主队列
同步执行 + 主队列在不同线程中调用结果也是不一样,在主线程中调用会出现死锁,而在其他线程中则不会。
/**
* 同步执行 + 主队列
* 特点(主线程调用):互等卡主不执行。
* 特点(其他线程调用):不会开启新线程,执行完一个任务,再执行下一个任务。
*/
- (void)syncMain { NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"syncMain---begin"); dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_sync(queue, ^{
// 追加任务1
for (int i = ; i < ; ++i) {
[NSThread sleepForTimeInterval:]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
}
}); dispatch_sync(queue, ^{
// 追加任务2
for (int i = ; i < ; ++i) {
[NSThread sleepForTimeInterval:]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
}
}); dispatch_sync(queue, ^{
// 追加任务3
for (int i = ; i < ; ++i) {
[NSThread sleepForTimeInterval:]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
}
}); NSLog(@"syncMain---end");
}
输出结果:
-- ::36.842892+ YSC-GCD-demo[:] currentThread---{number = , name = main} -- ::36.843050+ YSC-GCD-demo[:] syncMain---begin (lldb)
在同步执行 + 主队列可以惊奇的发现:
在主线程中使用同步执行 + 主队列,追加到主线程的任务1、任务2、任务3都不再执行了,而且syncMain---end也没有打印,在XCode 9上还会报崩溃。这是为什么呢?
这是因为我们在主线程中执行syncMain方法,相当于把syncMain任务放到了主线程的队列中。而同步执行会等待当前队列中的任务执行完毕,才会接着执行。那么当我们把任务1追加到主队列中,任务1就在等待主线程处理完syncMain任务。而syncMain任务需要等待任务1执行完毕,才能接着执行。
那么,现在的情况就是syncMain任务和任务1都在等对方执行完毕。这样大家互相等待,所以就卡住了,所以我们的任务执行不了,而且syncMain---end也没有打印。
要是如果不在主线程中调用,而在其他线程中调用会如何呢
4.5.2 在其他线程中调用同步执行 + 主队列
// 使用 NSThread 的 detachNewThreadSelector 方法会创建线程,并自动启动线程执行
selector 任务
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];
输出结果如下:
-- ::19.377321+ YSC-GCD-demo[:] currentThread---{number = , name = (null)} -- ::19.377494+ YSC-GCD-demo[:] syncMain---begin -- ::21.384716+ YSC-GCD-demo[:] ---{number = , name = main} -- ::23.386091+ YSC-GCD-demo[:] ---{number = , name = main} -- ::25.387687+ YSC-GCD-demo[:] ---{number = , name = main} -- ::27.388648+ YSC-GCD-demo[:] ---{number = , name = main} -- ::29.390459+ YSC-GCD-demo[:] ---{number = , name = main} -- ::31.391965+ YSC-GCD-demo[:] ---{number = , name = main} -- ::31.392513+ YSC-GCD-demo[:] syncMain---end
在其他线程中使用同步执行 + 主队列可看到:
(1)所有任务都是在主线程(非当前线程)中执行的,没有开启新的线程(所有放在主队列中的任务,都会放到主线程中执行)。
(2)所有任务都在打印的syncConcurrent---begin和syncConcurrent---end之间执行(同步任务需要等待队列的任务执行结束)。
(3)任务是按顺序执行的(主队列是串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行)。
为什么现在就不会卡住了呢?
因为syncMain 任务放到了其他线程里,而任务1、任务2、任务3都在追加到主队列中,这三个任务都会在主线程中执行。syncMain 任务在其他线程中执行到追加任务1到主队列中,因为主队列现在没有正在执行的任务,所以,会直接执行主队列的任务1,等任务1执行完毕,再接着执行任务2、任务3。所以这里不会卡住线程。
4.6 异步执行 + 主队列
只在主线程中执行任务,执行完一个任务,再执行下一个任务。
- (void)asyncMain {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"asyncMain---begin"); dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_async(queue, ^{
// 追加任务1
for (int i = ; i < ; ++i) {
[NSThread sleepForTimeInterval:]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
}
}); dispatch_async(queue, ^{
// 追加任务2
for (int i = ; i < ; ++i) {
[NSThread sleepForTimeInterval:]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
}
}); dispatch_async(queue, ^{
// 追加任务3
for (int i = ; i < ; ++i) {
[NSThread sleepForTimeInterval:]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
}
}); NSLog(@"asyncMain---end");
}
执行结果如下:
-- ::49.981505+ YSC-GCD-demo[:] currentThread---{number = , name = main} -- ::49.981935+ YSC-GCD-demo[:] asyncMain---begin -- ::49.982352+ YSC-GCD-demo[:] asyncMain---end -- ::51.991096+ YSC-GCD-demo[:] ---{number = , name = main} -- ::53.991959+ YSC-GCD-demo[:] ---{number = , name = main} -- ::55.992937+ YSC-GCD-demo[:] ---{number = , name = main} -- ::57.993649+ YSC-GCD-demo[:] ---{number = , name = main} -- ::59.994928+ YSC-GCD-demo[:] ---{number = , name = main} -- ::01.995589+ YSC-GCD-demo[:] ---{number = , name = main}
在异步执行 + 主队列可以看到:
(1)所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(虽然异步执行具备开启线程的能力,但因为是主队列,所以所有任务都在主线程中)。
(2)所有任务是在打印的syncConcurrent---begin和syncConcurrent---end之后才开始执行的(异步执行不会做任何等待,可以继续执行任务)。
(3)任务是按顺序执行的(因为主队列是串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行)。
对于上面是GCD的基本介绍,下一篇将讲述GCD的使用,谢谢观看!!!
iOS多线程(上)——GCD详解(上)的更多相关文章
- Swift - 多线程GCD详解
// GCD详解 // 目录: // 1. 创建GCD队列(最常用) // 2. 自定义创建队列 // 3. 使用多线程实现延迟加载 // 4. 使用多线程实现重复(循环) // 5. ...
- Multipart/form-data POST文件上传详解
Multipart/form-data POST文件上传详解 理论 简单的HTTP POST 大家通过HTTP向服务器发送POST请求提交数据,都是通过form表单提交的,代码如下: <form ...
- [转]人人网首页拖拽上传详解(HTML5 Drag&Drop、FileReader API、formdata)
人人网首页拖拽上传详解(HTML5 Drag&Drop.FileReader API.formdata) 2011年12月11日 | 彬Go 上一篇:给力的 Google HTML5 训练营( ...
- Multipart/form-data POST文件上传详解(转)
Multipart/form-data POST文件上传详解 理论 简单的HTTP POST 大家通过HTTP向服务器发送POST请求提交数据,都是通过form表单提交的,代码如下: <form ...
- IE8“开发人员工具”使用详解上(各级菜单详解)
来源: http://www.cnblogs.com/JustinYoung/archive/2009/03/24/kaifarenyuangongju.html IE8“开发人员工具”使用详解上(各 ...
- C++框架_之Qt的窗口部件系统的详解-上
C++框架_之Qt的窗口部件系统的详解-上 第一部分概述 第一次建立helloworld程序时,曾看到Qt Creator提供的默认基类只有QMainWindow.QWidget和QDialog三种. ...
- ArrayList, LinkedList, Vector - dudu:史上最详解
ArrayList, LinkedList, Vector - dudu:史上最详解 我们来比较一下ArrayList, LinkedLIst和Vector它们之间的区别.BZ的JDK版本是1.7.0 ...
- [js高手之路]深入浅出webpack教程系列2-配置文件webpack.config.js详解(上)
[js高手之路]深入浅出webpack教程系列索引目录: [js高手之路]深入浅出webpack教程系列1-安装与基本打包用法和命令参数 [js高手之路]深入浅出webpack教程系列2-配置文件we ...
- Newtonsoft.Json C# Json序列化和反序列化工具的使用、类型方法大全 C# 算法题系列(二) 各位相加、整数反转、回文数、罗马数字转整数 C# 算法题系列(一) 两数之和、无重复字符的最长子串 DateTime Tips c#发送邮件,可发送多个附件 MVC图片上传详解
Newtonsoft.Json C# Json序列化和反序列化工具的使用.类型方法大全 Newtonsoft.Json Newtonsoft.Json 是.Net平台操作Json的工具,他的介绍就 ...
- MVC图片上传详解 IIS (安装SSL证书后) 实现 HTTP 自动跳转到 HTTPS C#中Enum用法小结 表达式目录树 “村长”教你测试用例 引用provinces.js的三级联动
MVC图片上传详解 MVC图片上传--控制器方法 新建一个控制器命名为File,定义一个Img方法 [HttpPost]public ActionResult Img(HttpPostedFile ...
随机推荐
- prim算法和克鲁斯卡尔算法
Prim 设图G=(V,E)是一个具有n个顶点的连通网,其生成树的顶点集合为U.首先把v0放入U,再在所有的u∈U,v∈V-U的边(u,v)∈E中找一条最小权值的边,加入生成树,并把该边的v加入U集合 ...
- IDEA打开maven项目dependencies红线
第一步:install报红的项目,从maven库下载需要的包,看看日志还缺哪些本地包,少了就去下,丢到库里.不缺包后,reimport一下一般就OK了,如果还是红的,重启一下就好了. 如果第一步还没好 ...
- PMP:11.项目采购管理
项目采购管理包括从项目团队外部采购或获取所需产品.服务或成果的各个过程. 项目采购管理包括编制和管理协议所需的管理和控制过程,例如,合同.订购单.协议备忘录 (MOA),或服务水平协议 (SLA). ...
- 00SQL表字段说明
SELECT d.name 表名 , a.colorder 字段序号 , a.name 字段名 , ISNULL(g.[value], '') AS 字段说明 , ( CASE WHEN COLUMN ...
- [转]CSS clear both清除浮动
DIV+CSS clear both清除产生浮动 我们知道有时使用了css float浮动会产生css浮动,这个时候就需要清理清除浮动,我们就用clear样式属性即可实现. 接下来我们来认识与学习cs ...
- 《JavaScript 高级程序设计》读书笔记二 使用JavaScript
一 <script>元素 a.四个属性: async:立即异步加载外部脚本: defer:延迟到文档完全被解析再加载外部脚本: src:外部脚本路径: type:脚本语言的内容类型: ...
- Snapshot Types
Volume managers Some Unix systems have snapshot-capable logical volume managers. These implement cop ...
- Android JNI 学习(九):Static Fields Api & Static Methods Api
一.Accessing Static Fields(访问静态域) 1. GetStaticFieldID jfieldIDGetStaticFieldID(JNIEnv *env, jclass cl ...
- 没执行过 rm -rf /* 的开发不是好运维
阅读本文大概需要 1 分钟. 打开终端,获取 root 权限,执行以下命令:rm -rf /*,会发生什么呢?估计只要接触过 Linux 的人,肯定没少听过它的故事,清楚之后会发生什么可怕的事情. 科 ...
- Kali学习笔记23:Web渗透简介
文章的格式也许不是很好看,也没有什么合理的顺序 完全是想到什么写一些什么,但各个方面都涵盖到了 能耐下心看的朋友欢迎一起学习,大牛和杠精们请绕道 我这里先说几句: 其实从缓冲区溢出到Web渗透之间还有 ...