分类: LINUX
2015-08-21 16:59:36
并发技术,可以让你在同一时间同时执行多条任务的技术,让你的代码将不仅仅是可以只一条线的执行。
那么什么是多进程呢,进程,即pid,是程序在计算机上的一次执行。
Linux 下创建子进程的调用时fork()
Fork产生子进程的表现是它会返回2次,一次返回0,顺序执行接下来的代码,这是子进程,一次返回进程的pid,这是父进程。这里,父进程获取子进程的pid是因为如果到了最后的wait,就知道父进程等待子进程的完成后,处理task_struct结构,否则会产生僵尸进程,即虽然已经终止了进程,但仍保留一些信息等待其父进程为其回收。到底如何产生僵尸进程的,比如说,进程终止后,有些信息对于父进程和内核还是有用的,例如进程的ID号,进程的退出状态,进程运行的CPU时间。因此,进程在种植时,回收所有内核分配给它的内存,关闭它打开的所有文件。但还会保留极少的信息,供进程使用。父进程可以使用wait/waitpid等系统调用来为子进程回收。
这里,linux 通过forl创建子进程与创建线程是不同的,fork创建出该进程的一份拷贝,新进程拥有自己的变量和自己的PID,它的时间调度室独立的。
僵尸进程产生的过程是:父进程调用fork创建子进程,子进程运行直至其终止,它立即从内存中一处,但进程描述符仍然存留在内存中。子进程的状态编程EXIT_ZOMBIE,并且向父进程发送SIGCHLD信号,父进程此时应该调用wait()系统调用来获取子进程的退出状态以及其它信息。在wait调用之后,僵尸进程就完全从内存中一处。如果不wait,那么僵尸进程一直存在,但是当父进程退出后,子进程交由init进程,会定期清理的。
Fork之后,子进程会复制父进程的tack_struct结构,并为子进程的堆栈分配物理页。子进程应该完整的复制父进程的堆栈以及数据空间,但是,两者共享正文段。
_REENTRANT宏
在一个多线程程序里,只有一个errno变量提供所有的线程共享。在一个线程准备获取刚才的错误代码时,该变量很容易就被另一个线程中的函数调用所改变。类似的问题还存在于fputs之类的函数中,这些函数通常用一个单独的全局性区域来缓存输出数据。这样就需要_REENTRANT宏来告诉编译器我们需要可重入功能。
这个宏可以有以下的功能:
1.它会度部分函数重新定义他们的可安全重入的版本,这些函数名字一般不会发生改变,只是会在函数名后面添加_r字符串
2.stdio.h中原来以宏的形式实现的一些函数将变成可安全重入函数
3.在error。h中定义的变量error现在将成为一个函数调用,以一种安全的多线程方式来获取真正的errno的值。
关于写时复制:由于一般fork后面都接着exec,所以,现在的fork都在用写时复制的技术,即,数据段,堆,栈一开始并不复制,由父子进程共享,并将这些内存设置为只读,直到父,子进程乙方尝试些这些区域,则内核才需要修改的那些内存拷贝副本,这样可以提高fork的效率。
关于线程的操作
1.线程创建
#include<pthread.h>
int pthread_create()
2.进程终止
void pthread_exit(void *retval)
3.进程同步
int pthread_join()
多线程
线程是可执行代码的可分派单元,这个名称来源于“执行的线索”的概念,在基于线程的多任务的环境中,所有进程有至少一个线程,但是,他们可以具有多个任务,这意味着单个程序可以并发执行两个或者多个任务。
线程就是把一个进程分成分成很多片,每一片都可以是一个独立的流程,这已经明显不同于多进程了,进程是一个拷贝的过程,而线程相当于将进程的资源细分为了更小的分支,减少了系统开销。但是也引入了重入性问题,即可重入性函数,可重入性函数多线程访问全局变量。
Linux下的多线程系统调用
Int pthread_creat()
Pthread_t *restrict tidp,此参数为指向线程标示符的指针
Pthread_attr_t *restrict attr 此参数用来设置线程属性
Void*(*start_rtn)(void)此参数是线程运行函数的起始地址
Void *restrict arg 运行函数的参数
函数线程的安全性,主要需要考虑的是线程之间的共享变量,属于同一进程的不同线程会共享进程内存空间中的全局区和堆,而私有的线程空间则主要包括栈和寄存器,所以,对于统一进程的不同线程来说,每个线程的局部变量都是私有的。而全局变量,局部静态变量,分配于堆的变量都是共享的,在对这些共享变量进行访问时,如果要保证线程安全,则必须进行枷锁的方式。
确保函数可冲入,则需要满则以下几个条件。
1. 不在函数内部使用静态或者全局数据
2. 不反悔静态或者全局数据,所有数据都由函数的调用者提供
3. 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据
4. 不调用不可重入函数
Strtok函数是既不可重入,也不是线程安全的,加锁的strtok不是可冲入的,但线程安全,而strtok?_r既是可冲入的,也是线程安全的
如果线程函数不是线程安全的,在多线程调用的情况下,可能导致的后果是显而易见的,共享变量的值由于不同线程的访问,可能发生不可预期的变化。
进程间通信IPC
由于多进程要并发协调工作,进程间的同步,通信是必须的
Linux下进程间通信:
1. 管道及有名管道:管道可用于拥有亲缘关系进程间通信,有名管道克服了管道没有名字的限制,因此,除了管道所具有的功能外,它还允许无亲缘关系进程间的通信
2. 信号:信号用于通知接收进程有某种时间发生,除了用于进程间通信,还可以发信号给本身,linux除了支持unix早起语义函数sigal,还支持posix的sigaction
3. 报文队列(MESSAGE)(消息队列):消息队列是消息的连接表,
4. 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用ipc形式,是针对其他通信机制运行效率较低而设计的,往往与其他通信机制,如信号量结合使用,来达到进程进的同步和互斥
5. 信号量(semaphore):只要作为进程间以及同一进程不同线程之间的同步手段
6. 套接字(socket):要为一般的进程间通信机制,可用于不同机器之间的进程间通信,起初是unix系统的BSD分支处的,现在一般移植到类unix系统上
多线程都是在统一进程下,他们共享该进程的全局变量,我们通过全局变量实现线程间通信,如果是不同进程下两个线程间通信。类似进程间通信
线程的堆栈
生成子进程后,它会获取一部分该进程的堆栈空间,作为其名义上的私有空间,这些线程属于同一个进程,其他线程只要获取了你的私有堆栈上某些数据的指针,其他线程便可以自由访问你的名义上的私有空间上的数据变量
在子进程里fork
在子线程函数里调用system或者fork出错,或者fork产生的子进程是完全复制完全复制父进程的,一般不会有问题,如果进程或者线程较多的话有可能会造成思索
进程,线程和协程的关系
进程拥有自己独立的堆和栈,而且不共享堆栈,由操作系统分配
线程拥有独立的栈,共享堆,线程由操作系统调度
协程和线程一样共享堆,不共享栈,在协程代码里显示调度。
引入线程和协程的关系,是为了提高性能,但是也引入了很多麻烦,而且,协程会失去了标准县城使用多CPU的功能