并发的意义
概念:
只要逻辑控制流在时间上重叠,那么就可以称为并发。
意义:
访问慢速设备(如I/O设备):CPU可以在这样的慢速中“腾出手”再去做其他事情,使自己保持“繁忙”。
与人交互:每次用户进行某种操作的请求的时候用户不会在意在自己进行操作的时候系统是否还处理着其他程序,一个独立的逻辑并发流被用来处理这个程序。
通过推迟工作来降低延迟 服务多个网络客户端
进程:每个逻辑控制流都是一个进程,由内核进行调度和维护。
进程间的通信也必须有显式的通信机制。
每个进程都有独立的虚拟地址空间,发生交流的时候肯定要有机制
i/O多路复用:在并发编程中,一个程序在上下文中调用它们自己的逻辑流。因为程序是一个单独的进程,所以所有的流共享一个地址空间。即,对于从该进程所属的数据空间内进出的I/O道路上“运输”的数据是要提供给多个逻辑流的
线程:像进程一样由内核调用,像I/O多路复用一样共享同样的虚拟地址空间 2.构造并发服务器过程 (假设是一个服务器端两个客户端,服务器端始终监听)服务器正在监听一个描述符3,客户端1向服务器端提出申请,服务器端返回一个已经建立连接描述符4; 服务器派生一个子进程处理该连接。子进程拥有从父进程服务器描述符列表中完整的拷贝。,
父进程与子进程需要各自切断连接:父进程切断它的描述符列表中的连接描述符4,子进程切断它拷贝的监听描述符3; 服务器接收新的客户端2的连接请求,同样返回连接描述符5,派生一个子进程; 服务器端继续等待请求,两个子进程并发地处理客户端连接。 基于进程的并发编程的优缺点
优点:进程不可能不小心覆盖另一个进程的虚拟存储器。
缺点:独立的地址空间使得进程共享状态信息变得更加困难。为了共享信息,它们必须使用显示的IPC(进程间通信)机制,而进程控制和IPC的开销很高,因此会比较慢。
基于I/O多路复用技术的并发编程的优缺点
优点: 它比基于进程的设计给了程序员更多的对程序行为的控制。 每个逻辑流都能访问该进程的全部地址空间,这使得流之间共享数据变得容易。
缺点:编码复杂。
线程与线程池
线程不是按照严格的父子层次来组织的。和一个线程相关的线程组成一个对等(线程)池独立于其他线程创建的线程。
主线程和其他线程的区别仅在于它总是进程中第一个运行的线程。在对等线程池中,一个线程可以杀死它的任何对等线程,或者等待它的任意对等线程终止。另外,每个对等线程都能读写相同的共享数据
台因特网主机都运行实现TCP/IP协议的软件。
因特网的客户端和服务器混合使用套接字接口函数和Unix I/O函数来进行通信。
TCP/IP实际是一个协议组,其中每一个都提供不同的功能。
htonl函数将32位整数由主机字节顺序转换成网络字节顺序。
ntohl函数将32位整数从网络字节顺序转换成主机字节。
htons函数和ntohs为16位的整数执行相应的转换。
可以使用hostname -i来确定自己主机的点分十进制地址
对于IP地址人性化的命名为域名。
DNS域名系统
可以用hostinfo程序来挖掘一些DNS映射的特性。
hostname确定自己的主机域名。
因特网客户端和服务器通过在连接上发送和接收字节流来通信。
一个套接字是连接的一个断点,每个套接字都由相应的套接字地址,是由一个因特网地址和一个16位的整数端口组成的,用“地址:端口”来表示。
在Unix机器上,文件/etc/services包含一张这台机器提供的服务以及它们的知名端口号的综合列表。
一个链接是由它两端的套接字地址唯一确定的,称为套接字对。
sockaddr_in的16字节结构
sin_family成员是AF_INET
sin_port成员是一个16位的端口号
sin_addr成员是一个32位的IP地址。
IP地址和端口号总时以网络字节顺序(大端法)存放的。
_in是互联网络的缩写,不是输入input的缩写。
以下函数刘念老师的课上都说明过,仅作记录
socket函数 p626
connect函数 p626
open_clientfd函数 p627
bind函数 p628
listen函数 p628
open_listenfd函数 p628
accept函数 p629
Web服务器使用HTTP协议和它们的客户端(浏览器等)彼此通信。
浏览器向服务器请求静态或者动态的内容
对静态内容的请求是通过从服务器磁盘取得文件并把它返回给客户端来服务的。
对动态内容的请求时通过在服务器上一个子进程的上下文中运行一个程序并将它的输出返回给客户端来服务的。
CGI标准提供了一组规则,来管理客户端如何将程序参数传递给服务器。服务器如何将这些参数以及其他信息传递给子进程,以及子进程如何将它的输出发送回客户端。
使用应用级并发的应用程序称为并发程序。
现代操作系统提供了三种基本的构造并发程序的方法:
进程
I/O多路复用
线程
对于在父、子进程间共享状态信息,进程有一个非常清晰的模型:共享文件表,但是不共享用户地址空间。
独立的抵制空间使得进程共享状态信息变得更加困难。为了共享信息,它们必须使用显式的IPC(进程间通信)机制。因此它们往往比较慢。
i/O多路复用技术
使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才返回控制给应用。
select函数处理类型为fd_set的集合,也叫做描述符集合。
select函数有两个输入
读集合
读集合的基数n(实际上是任何描述符集合的最大技术)。
select函数会一直阻塞,直到读集合中至少有一个描述符准备好可以读。
当且仅当一个从该描述符读取一个字节的请求不会阻塞时,描述符k就表示准备好可以读了。
select函数的返回值指明了准备好集合的基数。
我们必须每次调用select时都更新读集合。
一个状态机就是一组状态、输入事件和转移
转移就是将状态和输入事件映射到状态。
自循环是同一个输入和输出状态之间的转移。
服务器使用I/O多路复用,借助select函数检测输入事件的发生。
霍东阁客户端的集合维护在一个pool结构中。
通过调用init_pool初始化池之后,服务器进入一个无限循环。
在循环的每次迭代中,服务器调用selct函数来检测两种不同类型的输入事件:
来自一个新客户端的连接请求到达
一个已存在的客户端的已连接描述符准备号可以读了。
当一个连接请求到达时,服务器打开连接,并调用add_client函数,将该客户端添加到池里。
最后服务器调用check_clients函数,把来自每个准备好的已连接描述符的一个文本行回送回去。
线程就是运行在进程上下文中的逻辑流。
程序都是由每个进程中一个现成组成的 。
每个现成都有自己的线程上下文,包括一个唯一的整数线程IP、栈、栈指针、程序计数器、通用目的寄存器和条件码。所有的运行在一个进程里的线程共享该流程的整个虚拟地址空间。
线程执行模型
每个进程开始生命周期时都是单一线程,这个线程称为主线程。
在某个时刻,主线程创建一个对等线程,从这个时间点开始,两个线程就并发地运行。
因为主线程执行一个慢速系统调用,控制就会通过上下文切换传递到对等线程。
Posix线程是在C程序中处理线程的一个标准接口。
线程的代码和本地数据被封装在一个线程例程中。
每个线程例程都以一个通用指针作为输入,并返回一个通用指针。
本地变量tid,可以用来存放对等线程的现成ID。
主线程通过调用pthread_create函数创建一个新的对等线程。
通过嗲用pthread_join,主线程等待对等线程终止。
主线程调用exit,终止当时运行在这个进程中的所有线程。
一个线程是以下之一来终止的:
当顶层的线程例程返回时,线程会隐式地终止。
通过调用pthread_exit函数,线程会显示地终止。
如果主线程调用thread_exit,它会等待所有其他对等线程终止,然后再终止主线程和整个进程,返回值为thread_return。
某个对等线程嗲用Unix地exit函数,该函数终止进程以及所有与该进程相关的线程。
另一个对等线程通过以当前线程ID作为参数调用pthread_cancle函数来终止当前线程。
在任何一个时间点上,线程是可结合地或者是分离的。
pthread函数分离可结合线程tid。
线程能够通过以pthread_self()为参数的pthread_detach调用来分离它们自己。
pthread_once函数允许你初始化与线程例程相关的状态。
once_control变量是一个全局或者静态变量,总是初始化为PTHREAD_ONCE_INIT。
线程化的C程序中变量根据它们的存储类型被映射到虚拟存储器:
全局变量
本地自动变量
本地静态变量
一个变量可共享时,当且仅当它的一个实例被一个以上的线程引用。
将线程的循环代码分解成五个部分:
Hi:循环头部的指令块
Li:加载cnt
Ui:更新cnt
Si:存储cnt
Ti:尾部的指令块
程序通过调用sem_wait和sem_post函数来执行P和V操作。
P中测试和减1操作是不分割旳。
不保护共享变量的函数
保护跨越多个调用的状态的函数
返回指向静态变量指针的函数调用线程不安全函数的函数