我是靠谱客的博主 火星上唇膏,这篇文章主要介绍c语言线程任务队列,进程、线程、任务、队列,现在分享给大家,希望可以做个参考。

一、 进程

1.进程是操作系统分配资源的基本单元,是一个具有一定独立功能的程序关于某次数据集合的一次运行活动.

2.进程是指在系统中正在运行的一个应用程序,就是一段程序的执行过程,我们可以理解为手机上的一个app.

3.每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内,拥有独立运行所需的全部资源

二、 线程

1.线程是程序执行流的最小单元,是进程中的一个实体.

2.一个进程要想执行任务,必须至少有一条线程,应用程序启动的时候,系统会默认开启一条线程,也就是主线程

三、 进程和线程的关系

1.线程是进程的执行单元,进程的所有任务都在线程中执行

2.线程是 CPU 分配资源和调度的最小单位

3.一个程序可以对应多个进程(多进程),一个进程中可有多个线程,但至少要有一条线程

4.同一个进程内的线程共享进程资源

四、多线程的优缺点

多线程的优点:

能适当提高程序的执行效率

能适当提高资源利用率(CPU、内存利用率)

多线程的缺点:

开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),

如果开启大量的线程,会占用大量的内存空间,降低程序的性能

线程越多,CPU在调度线程上的开销就越大

程序设计更加复杂:比如线程之间的通信、多线程的数据共享

五、任务

任务就是执行操作的意思,也就是在线程中执行的那段代码。在 GCD 中是放在 block 中的。

执行任务有两种方式:同步执行(sync)和异步执行(async)

同步(Sync):同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行,即会阻塞线程。只能在当前线程中执行任务(是当前线程,不一定是主线程),不具备开启新线程的能力。

异步(Async):线程会立即返回,无需等待就会继续执行下面的任务,不阻塞当前线程。可以在新的线程中执行任务,具备开启新线程的能力(并不一定开启新线程)。如果不是添加到主队列上,异步会在子线程中执行任务

六、队列

队列(Dispatch Queue):这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务

在 GCD 中有两种队列:串行队列和并发队列。两者都符合 FIFO(先进先出)的原则。

两者的主要区别是:执行顺序不同,以及开启线程数不同。

串行队列(Serial Dispatch Queue):

同一时间内,队列中只能执行一个任务,只有当前的任务执行完成之后,才能执行下一个任务。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)。主队列是主线程上的一个串行队列,是系统自动为我们创建的

并发队列(Concurrent Dispatch Queue):

同时允许多个任务并发执行。(可以开启多个线程,并且同时执行任务)。

注意:并发队列的并发功能只有在异步(dispatch_async)函数下才有效

同步/异步和串行/并发

//同步分配一个任务到串行队列

- dispatch_sync(serial_queue,^{//任务});

//异步分配一个任务到串行队列

- dispatch_async(serial_queue,^{//任务});

//同步分配一个任务到并发队列

- dispatch_sync(concurrent_queue,^{//任务})

//异步分配一个任务到并发队列

- dispatch_async(concurrent_queue,^{//任务})

七、多线程

主要有三种:NSThread、NSoperationQueue、GCD

math?formula=%5ccolor%7bred%7d%7b%e4%b8%80%e8%88%ac%e5%bb%ba%e8%ae%ae%e5%bc%80%e5%90%af%e7%ba%bf%e7%a8%8b%e6%9d%a1%e6%95%b0%ef%bc%9a3%e8%87%b35%e6%9d%a1%e6%9c%80%e4%b8%ba%e5%90%88%e7%90%86%7d

1. NSThread:轻量级的,更加面向对象,可直接操作线程对象

有三种创建方式分别为:

math?formula=%5ccolor%7bred%7d%7b-init%e6%96%b9%e6%b3%95%7d,手动开辟子线程,要手动启动,需要我们自己管理该线程,不只是启动,还有该线程使用完毕后的资源回收。

math?formula=%5ccolor%7bred%7d%7b-detachnewthreadselector%e6%96%b9%e6%b3%95%7d,直接自动启动。

math?formula=%5ccolor%7bred%7d%7b-performselectorinbackground%e6%96%b9%e6%b3%95%7d,直接启动

// 当使用初始化方法出来的主线程需要start启动

[thread start];

// 可以为开辟的子线程起名字

thread.name = @"线程名称";

// 调整Thread的权限 线程权限的范围值为0 ~ 1 。越大权限越高,先执行的概率就会越高,由于是概率,所以并不能很准确的的实现我们想要的执行顺序,默认值是0.5

thread.threadPriority=1;

// 取消当前已经启动的线程

[thread cancel];

// 通过遍历构造器开辟子线程

[NSThread detachNewThreadSelector:@selector(testAction:) toTarget:self withObject:@"参数"];

//在当前线程。延迟1s执行。响应了OC语言的动态性:延迟到运行时才绑定方法

[self performSelector:@selector(testAction:) withObject:nil afterDelay:1];

// 回到主线程。

//waitUntilDone:是否将该回调方法执行完在执行后面的代码,

//如果为YES:就必须等回调方法执行完成之后才能执行后面的代码,说白了就是阻塞当前的线程;

//如果是NO:就是不等回调方法结束,不会阻塞当前线程

[self performSelectorOnMainThread:@selector(testAction) withObject:nil waitUntilDone:YES];

//开辟子线程

[self performSelectorInBackground:@selector(testAction) withObject:nil];

//在指定线程执行

[self performSelector:@selector(testAction) onThread:[NSThread currentThread] withObject:nil waitUntilDone:YES]

//带afterDelay的延时函数,会在内部创建一个 NSTimer,然后添加到当前线程的Runloop中。也就是如果当前线程没有开启runloop,该方法会失效。在子线程中,需要启动runloop(注意调用顺序)

//performSelector:withObject:只是一个单纯的消息发送,和时间没有一点关系。所以不需要添加到子线程的Runloop中也能执行

[self performSelector:@selector(testAction) withObject:nil afterDelay:1];

[[NSRunLoop currentRunLoop] run];

当调用 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。

当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。

2、GCD,充分利用多核,自动管理生命周期

队列

执行方式

串行

同步

并发

异步

主队列

组合方式

执行方式

创建线程

串行同步

顺序

否

串行异步

顺序

是

并发同步

顺序

否

并发异步

交替

是

主队列同步

死锁

否

主队列异步

顺序

否

有3中queue

1.主队列 dispatch_main_queue(); 串行,更新UI

2.全局队列 dispatch_global_queue(); 并行 四个优先级 background、low、default、high

3.自定义队列 dispatch_queue_t(); 串行或并行 DISPATCH_QUEUE_CONCURRENT、DISPATCH_QUEUE_SERIAL

3、NSOperation 基于GCD的封装,抽象类,不能直接使用,有两个子类可用

NSInvocationOperation,开启后在主线程执行.

NSBlockOperation,开启后在主线程执行,addExecutionBlock方法在子线程中执行

maxConcurrentOperationCount

值

执行方式

-1

并发执行,开多线程(默认)

0

无法执行任务

1

串行执行,不开启线程

>1

并发执行(设置很大没有意义)

4、GCD 和 NSOprationQueue的区别

GCD是面向底层的C语言的API

NSOpertaionQueue用GCD构建封装的,是GCD的高级抽象,完全面向对象,比GCD简单易用,代码可读性高。

1、GCD执行效率更高,而且由于队列中执行的是由block构成的任务,这是一个轻量级的数据结构,写起来更方便,GCD没法停⽌已经加⼊queue的block(其实是有的,但需要许多复杂的代码)

2、GCD只支持FIFO的队列,而NSOperationQueue可以通过设置最大并发数,设置优先级,添加依赖关系等调整执行顺序

3、NSOperationQueue甚至可以跨队列设置依赖关系,但是GCD只能通过设置串行队列,或者在队列内添加barrier(dispatch_barrier_async)任务,才能控制执行顺序,较为复杂,我们可以随时取消已经设定要准备执⾏的任务(当然,已经开始的任务就⽆法阻⽌了)

4、NSOperationQueue因为面向对象,所以支持KVO,可以监测operation是否正在执行(isExecuted)、是否结束(isFinished)、是否取消(isCanceld)

5.实例

math?formula=%5ccolor%7bred%7d%7b1.%e4%b8%b2%e8%a1%8c%e9%98%9f%e5%88%97%e4%b8%ad%e5%85%88%e5%bc%82%e6%ad%a5%e5%86%8d%e5%90%8c%e6%ad%a5%7d

NSLog(@"start--%@",[NSThreadcurrentThread]);

for(inti=0; i<10; i++) {

dispatch_async(q, ^{

NSLog(@"async--%@ i = %d",[NSThreadcurrentThread],i);

}); }

NSLog(@"end1--%@",[NSThreadcurrentThread]);

for(inti=0; i<10; i++) {

dispatch_sync(q, ^{

NSLog(@"sync--%@---%d",[NSThreadcurrentThread],i);

});

}

NSLog(@"end2--%@",[NSThreadcurrentThread]);

}

执行结果

6ff9e7d61c24

执行结果.jpg

分析:先在主线程中执行打印start,然后将异步任务添加到串行队列中(只是添加,并没有立即执行),之后在主线程打印end1,再然后往串行队列里添加了1个同步任务,此时主线程堵塞,等待串行队列里地同步任务执行完毕。此时创建一个子线程开始依次执行异步任务,异步任务结束后,在主线程中执行同步任务(上面的结论)。1个同步任务执行完后(此时串行队列里没有任务了),主线程返回,for循环继续往串行队列里添加1个同步任务,此时主线程继续阻塞,等待串行队列里同步任务执行完毕,此时主线程执行这个同步任务,执行完毕后,主线程返回继续for循环……。等for循环结束后,最后在主线程执行打印end2的操作。

2.并行队列中先异步再同步

NSLog(@"start--%@",[NSThreadcurrentThread]);

for(inti=0; i<10; i++) {

dispatch_async(q, ^{

NSLog(@"async--%@---%d",[NSThreadcurrentThread],i);

});

}

NSLog(@"end1--%@",[NSThreadcurrentThread]);

for(inti=0; i<10; i++) {

dispatch_sync(q, ^{

NSLog(@"sync--%@---%d",[NSThreadcurrentThread],i);

});

}

NSLog(@"end2--%@",[NSThreadcurrentThread]);

执行结果

6ff9e7d61c24

执行结果.jpg

分析:上面代码创建了一个并行队列,然后往并行队列中添加了10个异步任务和10个同步任务,上面只是某一次的打印结果,每次的打印结果都不一样,从打印结果可以看出:由于是并行队列,会开启多个子线程执行异步任务,所以异步任务的打印结果是无序的,而同步任务由于都是在主线程中执行,所有总体是有序的。而且同步与异步任务是交叉着执行完毕的。

3.死锁

dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);

dispatch_async(serialQueue, ^{

dispatch_sync(serialQueue, ^{

NSLog(@"123");

});

});

分析:外面的函数无论是同步还是异步都会造成死锁。 这是因为里面的任务和外面的任务都在同一个 serialQueue 队列内,又是同步,解决方法将里面的同步改成异步 dispatch_async,或者将 serialQueue 换成其他串行或并 行队列,都可以解决

NSLog(@"任务1");

dispatch_sync(dispatch_get_main_queue(), ^{

NSLog(@"任务2");

});

NSLog(@"任务三");

分析:先执⾏任务1,接下来,程序遇到了同步线程,它会进⼊等待,等待任务2执⾏完,然后执⾏任务3。但这是队列,有任务来,会将任务加到队尾,然后遵循FIFO原则执⾏任务,现在任务2就会被加到最后,任务3排在了任务2前⾯.

问题来了:任务3要等任务2执⾏完才能执⾏,任务2⼜排在任务3后⾯,意味着任务2要在任务3执⾏完才能执⾏,所以他们进⼊了互相等待的局⾯。

math?formula=%5ccolor%7bred%7d%7b4.performselector%7d

dispatch_async(dispatch_get_global_queue(0, 0), ^{

[self performSelector:@selector(test:) withObject:nil afterDelay:0];

});

分析:这里的 test 方法是不会去执行的,原因在于performSelector方法要创建提交任务到 runloop 上的,而GCD底层创建的线程默认是没有开启对应 runloop 的,所已 这个方法就会失效。 而如果将 dispatch_get_global_queue 改成主队列,由于主队列所在的主线程是默认开启runloop的, 就会去执行(将 dispatch_async 改成同步,因为同步是在当前线程执行,那么如果当前线程是主线程,test 方法也是会去执行的),也可以使用 GCD的dispatch_after来实现。

math?formula=%5ccolor%7bred%7d%7b5.dispatch_barrier_async%7d

可以实现多读单写,多读单写的意思是可以多个读者同时读取数据,而在读的时候,不能取写入数据。并且在写的过程中,不能有其他写者去写。即读者之间是并发的,写者与读者或其他写者是互斥的。 这里的写处理就是通过栅栏的形式去写。

dispatch_queue_t concurrentQueue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

for (NSInteger i = 0; i < 10; i++) {

dispatch_sync(concurrentQueue, ^{

NSLog(@"%zd",i);

});

}

dispatch_barrier_sync(concurrentQueue, ^{

NSLog(@"------barrier------");

});

for (NSInteger i = 10; i < 20; i++) {

dispatch_sync(concurrentQueue, ^{

NSLog(@"%zd",i);

});

}

分析:这里的 dispatch_barrier_sync 上的队列要和需要阻塞的任务在同一队列上,否则是无效的。 从打印上看,任务 0-9 和任务任务 10-19 因为是异步并发的原因,彼此是无序的。而由于栅栏函数的存在, 导致顺序必然是先执行任务 0-9,再执行栅栏函数,再去执行任务 10-19。

dispatch_barrier_sync: 提交一个栅栏函数在执行中,它会等待栅栏函数执行完

dispatch_barrier_async: 提交一个栅栏函数在异步执行中,它会立马返回)

而 dispatch_barrier_sync 和 dispatch_barrier_async 的区别也就在于会不会阻塞当前线程

比如,上述代码如果在 dispatch_barrier_async 后随便加一条打印,则会先去执行该打印,再去执 行任务 0-9 和栅栏函数;而如果是 dispatch_barrier_sync,则会在任务 0-9 和栅栏函数后去执行这 条打印。

math?formula=%5ccolor%7bred%7d%7b6.dispatch_group_async%7d

在 n 个耗时并发任务都完成后,再去执行接下来的任务。比如,在 n 个网络请求完成后去刷新 UI 页 面。

dispatch_queue_t concurrentQueue = dispatch_queue_create("test",DISPATCH_QUEUE_CONCURRENT);

dispatch_group_t group = dispatch_group_create();

for (NSInteger i = 0; i < 10; i++) {

dispatch_group_async(group, concurrentQueue, ^{

sleep(1);

NSLog(@"%zd",i);

});

}

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

NSLog(@"主线程");

});

math?formula=%5ccolor%7bred%7d%7b7.dispatch%20semaphore%7d

GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号,保持线程同步,将异步执行任务转换为同步执行任务,保证线程安全,为线程加锁

Dispatch Semaphore 提供了三个函数

dispatch_semaphore_create:创建一个 Semaphore 并初始化信号的总量

dispatch_semaphore_signal:发送一个信号,让信号总量加 1

dispatch_semaphore_wait:可以使总信号量减 1,当信号总量为 0 时就会一直等待(阻塞所在线程),否 则就可以正常执行。

math?formula=%5ccolor%7bred%7d%7b8.dispatch_after%7d

延时函数

dispatch_after 能让我们添加进队列的任务延时执行,该函数并不是在指定时间后执行处理,而只是在指 定时间追加处理到 dispatch_queue

内部使用的是 dispatch_time_t 管理时间,而不是 NSTimer。 所以如果在子线程中调用,相比 performSelector:afterDelay,不用关心 runloop 是否开启

math?formula=%5ccolor%7bred%7d%7b9.dispatch_once%7d

实现单利

+ (instancetype)sharInstance{

static dispatch_once_t onceToken;

static id instance = nil;

dispatch_once(&onceToken, ^{

instance = [[self alloc] init];

});

return instance;

}

math?formula=%5ccolor%7bred%7d%7b10.nsthread%2brunloop%20%e5%ae%9e%e7%8e%b0%e5%b8%b8%e9%a9%bb%e7%ba%bf%e7%a8%8b%7d

NSThread 在实际开发中比较常用到的场景就是去实现常驻线程。

由于每次开辟子线程都会消耗 cpu,在需要频繁使用子线程的情况下,频繁开辟子线程会消耗大量的 cpu,而且创建线程都是任务执行完成之后也就释放了,不能再次利用,最好是用常驻线程

+ (NSThread *)shareThread{

static NSThread *shareThread = nil;

static id instance = nil;

static dispatch_once_t oncePredicate;

dispatch_once(&oncePredicate, ^{

shareThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadTest1) object:nil];

[shareThread setName:@"threadTest"];

[shareThread start];

});

return shareThread;

}

+ (void)threadTest{

@autoreleasepool {

NSRunLoop *runLoop = [NSRunLoop currentRunLoop];

[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];

[runLoop run];

}

}

//然后调用 performSelector 就可以啦

[self performSelector:@selector(test) onThread:[ViewController shareThread] withObject:nil waitUntilDone:NO];

- (void)test {

NSLog(@"test:%@",[NSThread currentThread]);

}

6ff9e7d61c24

结构图.png

最后

以上就是火星上唇膏最近收集整理的关于c语言线程任务队列,进程、线程、任务、队列的全部内容,更多相关c语言线程任务队列内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(267)

评论列表共有 0 条评论

立即
投稿
返回
顶部