iOS-线程&&进程的深入理解

进程基本概念


  • 进程就是一个正在运行的一个应用程序;
  • 每一个进度都是独立的,每一个进程均在专门且手保护的内存空间内;
  • iOS是怎么管理自己的内存的,见博客:博客地址
  • 在Linux系统中,想要新开启一个进程是一件非常简单的事情只需要一句话:fork(),在fork()之后就会包含两个进程,此时可以根据返回的PID来判断是子进程还是父进程;
  • iOS中是一个非常封闭的系统,每一个App(一个进程)都有自己独特的内存和磁盘空间,别的App(进程)是不允许访问的(越狱不在讨论范围);
  • 所以,在iPhone中下载了两种音乐播放器,下载的音乐歌曲却是不能共享的!跟安卓完全不同!——有好有坏吧!
  • 如果想学习进程方面的知识,我觉得还是看看Linux对进程的描述,比如进程间的通信(管道、内存共享、实用信号量防止资源抢夺等)。

线程基本概念


- 线程是CPU调度的最小单元;
- 线程的作用:执行app的代码;
- 一个进程(App)至少有一个线程,这个进程叫做主线程;

线程和进程的关系

  • 进程和应用程序的关系:进程为应用程序开辟内存空间;
  • 线程和应用程序的关系:线程执行应用程序的代码;
  • 进程和线程之间的关系:进程是由线程组成的、一个进程理论上可以有很多个线程、但至少有一个主线程;

线程

在iOS中进程相关的操作并不是很多,常见的就App之间相互调用,苹果公司将这些操作都封装在了UIApplcation这个类中了。


为什么要使用多线程

CPU -> 进程 -> 线程;

如果是在Linux系统中,在讨论为什么在使用多进程时,是针对多进程考虑的,因为Linux支持多进程程序;而iOS开发中,仅仅就是真对一个(App)进程来开发的;

  1. 方便的通信和数据交换

    多进程程序结构和多线程程序结构有很大的不同;
    对不同进程来说,它们具有独立的数据空间,要 进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然, 由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用, 这不仅快捷,而且方便。

  2. 更高效的利用 CPU

    大多数操作系统是根据时间片轮转调度,在Linux/Unix中,CPU的调度事件是100ms;而线程是CPU最小的调度单元,也就是说当开启一个新线程时,CPU在自己的调度链表中去循环调度这个线程;如果一个线程没有,那么CPU就会休息;所以说,多线程能够适当提高CPU的利用率!当然CPU调用时并非这么简单,其中会包含调度的优先级、中断等来保证CPU调度是优化的!

    所以,在讨论为什么要使用多线程时,如果不说针对多进程而言是那就是没有没有参考对象。

  3. 当然,程序中并非开启越多的线程越好,首先线程需要消耗内存,主线程1M、子线程是512K;
    其次、线程越多,CPU的线程链表就越长,执行效率会变慢!适当的利用多线程!在iOS 中,苹果公司也为我们封装一个类`NSOperation`


多线程在iOS开发中的应用

  • 一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”

  • 主线程的主要作用

  • 显示\刷新UI界面
  • 处理UI事件(比如点击事件、滚动事件、拖拽事件等)

  • 主线程的使用注意

  • 别将比较耗时的操作放到主线程中
  • 耗时操作会卡住主线程,严重影响UI的流畅度,极差的用户体验!

在iOS的中多线程的实现方法

四种实现方式Threads、NSthread、NSOperation、GCD,使用频率由低到高!


  1. POSIX Threads

    • 众多软件供应商都为自己产品实现多线程库专有版本。这些线程库实现彼此独立并有很大差别,导致程序员难以开发可移植的多线程应用程序,因此必须要确立一个规范的编程接口标准来充分利用多线程所提供的优势,POSIX -Threads 就是这样一个规范多线程标准接口。

    • POSIX Threads(通常简称为Pthreads)定义了创建和操纵线程的一套 API 接口,一般 用于 Unix-like POSIX 系统中(如 FreeBSD、GNU/Linux、OpenBSD、Mac OS 等系统)。

    Pthreads 接口可以根据功能划分四个组:

    • 线程管理
    • 互斥量
    • 条件变量
    • 同步

Threads.1. 程管理线

程管理包含了线程的创建、终止、等待、分离、设置属性等操作。
每个线程都有从创建到终止的生命周期。
创建线程
在进程中创建一个新线程的函数是 pthread_create(),原型如下:
```
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

```
说明:线程被创建后将立即运行。
如果pthread_create()调用成功,函数返回0,否则返回一个非0的错误码
参数说明:
    thread用指向新创建的线程的ID;
    attr用来表示一个封装了线程各种属性的属性对象,如果attr为NULL,新线程就 使用默认的属性,13.3.4 节将讨论线程属性的细节;
    start_routine是线程开始执行的时候调用的函数的名字,start_routine函数有一个有 指向 void 的指针参数,并有 pthread_create 的第四个参数 arg 指定值;start_routine 函数返回一个指向 void 的指针,这个返回值被 pthread_join 当做退出 状态处理;
    arg为参数start_routine指定函数的参数。
2. 终止线程
    void pthread_exit(void *retval);

线程安全

多线程编程环境中,多个线程同时调用某些函数可能会产生错误结果,这些函数称为非线程安全函数。如果库函数能够在多个线程中同时执行并且不会互相干扰,那么这个库函数 就是线程安全(thread-safe)函数;
  • Threads.2. 互斥量
    在操作系统中有许多共享资源不允许用户并行使用,例如手机的通话,在通话过程中,不能同时去接两个电话,香这种共享设备被称为“排它性资源”,因为它一次只能由一个执行流访问。执行流必须以互斥的方式执行访问排它性资源的代码。
    临界区是必须以互斥方式执行的代码段,也就是说在临界区的范围内只能有一个活动的执行线程。

    互斥量(Mutex),又称为互斥锁,是一种用来保护临界区的特殊变量,它可以处于锁定(locked)状态,也可以处于解锁(unlocked)状态:
    如果互斥锁是锁定的,就是一个特定的线程持有这个互斥锁;
    如果没有线程持有这个互斥锁,那么这个互斥锁就处于解锁状态。
    每个互斥锁内部有一个线程等待队列,用来保存等待该互斥锁的线程。当互斥锁处于解锁状态时,一个线程试图获取这个互斥锁时,这个线程就可以得到这个互斥锁而不会阻塞;

    当互斥锁处于锁定状态时,一个线程试图获取这个互斥锁时,这个线程将阻塞在互斥锁的等 待线程队列内。

    互斥量是最简单也是最有效的线程同步机制。程序可以用它来保护临界区,以获得对排它性资源的访问权。
    另外,互斥量只能被短时间地持有,使用完临界资源后应立即释放锁。

        pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;
        pthread_mutex_lock(&mylock);
        临界区代码
        pthread_mutex_unlock(&mylock);
    

Threads.3. 条件变量
在多线程编程中仅使用互斥锁来完成互斥是不够用的,如以下情形:
假设有两个线程 t1 和 t2,需要这个两个线程循环对一个共享变量 sum 进行自增操作, 那么 t1 和 t2 只需要使用互斥量即可保证操作正确完成,线程执行代码如所示:

```
pthread_mutex_t sumlock= PTHREAD_MUTEX_INITIALIZER;
void * t1t2(void) {
    thread_mutex_lock(&sumlock);
    sum++;
    pthread_mutex_unlock(&sumlock); 

}
```
如果这时需要增加另一个线程 t3,需要 t3 在 count 大于 100 时将 count 值重新置 0 值, 那么可以 t3 可以实现如下:
```
void * t3 (void) { pthread_mutex_lock(&sumlock); 
    if (sum >= 100) {
      sum = 0;
      pthread_mutex_unlock(&sumlock); 
    } else {
      pthread_mutex_unlock(&sumlock);
      usleep(100); 
    }
}
```
    以上代码存在以下问题:
    1) sum 在大多数情况下不会到达 100,那么对 t3 的代码来说,大多数情况下,走的是 else分支,只是 lock 和 unlock,然后 sleep()。这浪费了 CPU 处理时间。
    2) 为了节省 CPU 处理时间,t3 会在探测到 sum 没到达 100 的时候 usleep()一段时间。 这样却又带来另外一个问题,亦即 t3 响应速度下降。可能在 sum 到达 200 的时候,t3 才会 醒过来。
    这样时间与效率出现了矛盾,而条件变量就是解决这个问题的好方法。

    在Threads这种场景就需要用等待和通知去解决!
    条件等待函数有 pthread_cond_wait()pthread_cond_timedwait()和两个条件通知函数有 pthread_cond_signal()和 pthread_cond_broadcast()函数;


    其实这种场景在iOS开发中,就是进程中有相互依赖的关系,A执行完毕,B才能执行;NSOperation中就可以添加依赖,使得A完成之后B才能去执行;

    如果在不同的队列中,那么就需要用GCD的dispatch_group_t(队列)组去解决!
    那么我觉得dispatch_group_t就是对Threads的通知等待的封装。

你可能感兴趣的:(前端,多线程&多进程)