您的位置:首页 > 其它

《后台开发核心技术与应用实践》(四)

2017-04-24 22:28 597 查看
多线程
1 多线程

2多线程的创建与结束

3 线程的属性

4 多线程的同步

5 多线程的重入

进程
1程序与进程

2进程的创建与结束

3僵尸进程

4守护进程

进程间通信
1管道

2消息队列

3共享内存

4信号量

5ipcs命令

9. 多线程

9.1. 多线程

(1)多进程频繁上下文切换引起的额外开销可能会严重影响系统性能;进程间通信要求复杂的系统级实现

(2)同一个进程内部的多个线程共享该进程的所有资源;通过线程可以支持同一个应用程序内部的并发,免去了进程频繁切换的开销;并发任务间通信也更简单。

(3)多线程在的进程在内存中有多个栈,每个栈对应一个线程,多个栈之间以一定的空白区域隔开,以备栈的增长,任何一个空白区域被填满都会导致栈溢出。

9.2.多线程的创建与结束

(1)线程创建:pthread_create函数

(2)线程退出:执行完成后隐式退出;由线程本身显示调用pthread_exit 函数退出;被其他线程用pthread_cancel函数终止

(3)pthread_join用于等待一个线程的结束,也就是主线程中要是加了这段代码,就会在加代码的位置卡主,直到这个线程执行完毕才往下走。一般都是pthread_exit在线程内退出,然后返回一个值。这个时候就跳到主线程的pthread_join了(因为一直在等你结束),这个返回值会直接送到pthread_join,实现了主与分线程的通信。

(4)向线程传递参数

(5)获得线程id

9.3. 线程的属性

(1)属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用,之后必须用pthread_attr_destroy函数来释放资源。

(2)属性对象主要包括:作用域(scope)、栈尺寸(stack size)、栈地址(stack address)、优先级(priority)、分离的状态(detached state)、调度策略和参数(scheduling policy and parameters)。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。

9.4. 多线程的同步

(1)互斥锁。通过加锁将原先分离的多个指令构成不可分割的一个原子操作

(2)条件变量。条件变量本身不是锁!但它也可以造成阻塞。通常与互斥锁配合使用,给多线程提供一个会合的场所。

(3)读写锁。与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享。

(4)信号量。进化版的互斥锁(1–>N)。信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。

9.5. 多线程的重入

(1)可重入函数特点:

1.不在函数内部使用静态或者全局数据

2.不返回静态或者全局数据,所有的数据都由函数调用者提供

3.使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据

4. 如果必须访问全局数据,使用互斥锁来保护

5.不调用不可重入函数

(2)不可重入函数特点:

1.函数中使用了静态变量,无论是全局静态变量还是局部静态变量

2.函数返回静态变量

3.函数中调用了不可重入函数

4.函数中使用了静态的数据结构

5.调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的

6.调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

(3)_REENTRANT宏

在一个多线程程序里,默认情况下,只有一个errno变量供所有的线程共享。在一个线程准备获取刚才的错误代码时,该变量很容易被另一个线程中的函数调用所改变。类似的问题还存在于fputs之类的函数中,这些函数通常用一个单独的全局性区域来缓存输出数据。

为解决这个问题,需要使用可重入的例程。可重入代码可以被多次调用而仍然工作正常。编写的多线程程序,通过定义宏_REENTRANT来告诉编译器我们需要可重入功能,这个宏的定义必须出现于程序中的任何#include语句之前。

_REENTRANT为我们做三件事情,并且做的非常优雅:

1.它会对部分函数重新定义它们的可安全重入的版本,这些函数名字一般不会发生改变,只是会在函数名后面添加_r字符串,如函数名gethostbyname变成gethostbyname_r。

2.stdio.h中原来以宏的形式实现的一些函数将变成可安全重入函数。

3.在error.h中定义的变量error现在将成为一个函数调用,它能够以一种安全的多线程方式来获取真正的errno的值。

10.进程

10.1.程序与进程

(1)进程结构:代码段、数据段、堆栈段。堆栈段包括进程控制块PCB

(2)程序转换成进程步骤:

1.内核将程序读入内存,为程序分配内存空间

2.内核为该进程分配进程标识符(PID)和其他所需资源

3.内核为进程保存PID及相应的状态信息,把进程放到运行队列中等待执行。程序转化为进程后就可以被操作系统的调度程序调度执行了。

10.2.进程的创建与结束

(1)fork()函数创建进程。仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:

1.在父进程中,fork返回新创建子进程的进程ID;

2.在子进程中,fork返回0;

3.如果创建出错,fork返回-1

(2)exit()函数结束进程。

1.exit是函数,带参数,执行完后把控制权交给系统;return是函数执行完后的返回,执行完后把控制权交给调用函数

2.exit是正常终止进程;abort是异常终止

3.exit()会将缓冲区的数据写完后再退出;_exit()函数直接退出

10.3.僵尸进程

(1)孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

(2)僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。

10.4.守护进程

(1)linux或UNIX在系统引导时会开启很多服务,这些服务就叫作守护进程。守护进程是脱离终端并且在后台运行的进程

(2)创建守护进程步骤:

1.创建子进程,父进程退出(使子进程成为孤儿进程)

2.在子进程中创建新的会话(脱离控制终端)

3.改变当前目录为根目录

4.重设文件权限掩码

5.关闭文件描述符

6.守护进程的退出

11.进程间通信

11.1.管道

(1)管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程或兄弟进程。

(2)无名管道和有名管道(FIFO)。前者用于父进程和子进程间的通信,后者用于运行于同一台机器上的任意两个进程间的通信。

11.2.消息队列

消息队列用于运行于同一台机器上的进程间通信,它和管道很相似,是一个在系统内核中用来保存消息的队列,它在系统内核中是以消息链表的形式出现。消息链表中节点的结构用msg声明。

11.3.共享内存

(1)共享内存是运行在同一台机器上的进程间通信最快的方式,因为数据不需要在不同的进程间复制。通常由一个进程创建一块共享内存区,其余进程对这块内存区进行读写。

(2)得到共享内存有两种方式:映射/dev/mem设备和内存映像文件。前一种方式不给系统带来额外的开销,但在现实中并不常用,因为它控制存取的将是实际的物理内存,在Linux系统下,这只有通过限制Linux系统存取的内存才可以做到,这当然不太实际。常用的方式是通过shmXXX函数族来实现利用共享内存进行存储的。

(3)使用共享内存优点:方便,函数接口简单,数据的共享使得进程间的数据不用传送,而是直接访问,也加快了效率。不像无名管道一样要求亲缘关系。

(4)使用共享内存缺点:没有提供同步机制,使得在使用共享内存进行进程间通信时,往往要借助其他的手段来进行进程间的同步工作。

11.4.信号量

信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是前一节的共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。

11.5.ipcs命令

(1)ipcs -a列出本用户所有相关的ipcs参数

(2)ipcs -q列出进程中的消息队列

(3)ipcs -s列出所有信号量

(4)ipcs -m列出所有共享内存信息

(5)ipcs -l列出系统的限额

(6)ipcs -t列出最后的访问时间

(7)ipcs -u列出当前的使用情况

《后台开发核心技术与应用实践》(一)

《后台开发核心技术与应用实践》(二)

《后台开发核心技术与应用实践》(三)

《后台开发核心技术与应用实践》(四)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  多线程
相关文章推荐