进程线程相关

【进程和线程的区别】

进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

线程是进程的一个实体,是CPU调度和分派的基本单位,是比进程更小的能独立运行的基本单位,只拥有在运行中必不可少的资源(如程序计数器,寄存器和栈),可与同属一个进程的其他的线程共享进程所拥有的全部资源。对线程的调度所付出的开销会小得多,能更高效的提高系统内多个程序间并发执行的程度。

线程是进程中的一部分,同一个进程中的多个线程之间可以并发执行。相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。

进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

进程优点:编程、调试简单,可靠性较高。
进程缺点:创建、销毁、切换速度慢,内存、资源占用大。
线程优点:创建、销毁、切换速度快,内存、资源占用小。
线程缺点:编程、调试复杂,可靠性较差。

上面的对比可以归结为一句话:“线程快而进程可靠性高”。线程有个别名叫“轻量级进程”,在有的书籍资料上介绍线程可以十倍、百倍的效率快于进程; 而进程之间不共享数据,没有锁问题,结构简单,一个进程崩溃不像线程那样影响全局,因此比较可靠。

【多线程&多进程】

进程最直观的就是一个个pid。官方的说法:进程是程序在计算机上的一次执行活动。

进入main函数,就是一个进程,进程pid会打印出来,然后运行到return,该函数就退出,然后由于该函数是该进程的唯一的一次执行,所以return后,该进程也会退出。

1. 创建子进程

linux下创建子进程的调用是fork(),fork产生子进程的表现就是它会返回2次,子进程的返回值为0,因为子进程可以随时调用getpid()获取自己的pid,也可以调用getppid()获取父进程的id;父进程的返回值为子进程的pid,因为进程的子进程可以多于一个,没有一个函数使得一个进程可以获得所有子进程的id。如果fork失败,会返回-1。
为何父进程需要获取子进程的pid呢?这个有很多原因,其中一个原因:看最后的wait,就知道父进程等待子进程的终结后,处理其task_struct结构,否则会产生僵尸进程。
fork后,子进程会复制父进程的task_struct结构,并为子进程的堆栈分配物理页。理论上来说,子进程应该完整地复制父进程的堆,栈以及数据空间,但是2者共享正文段。
关于写时复制:由于一般 fork后面都接着exec,所以,现在的 fork都在用写时复制的技术,顾名思意,就是数据段,堆,栈,一开始并不复制,由父,子进程共享,并将这些内存设置为只读。直到父,子进程一方尝试写这些区域,则内核才为需要修改的那片内存拷贝副本。这样做可以提高 fork的效率。

线程就是把一个进程分为很多片,每一片都可以是一个独立的流程,系统开销少。

多线程系统调用:


http://blog.csdn.net/hairetz/article/details/4281931/

linux下要编译使用线程的代码,一定要记得调用pthread库。如下编译:

 gcc -o pthrea -pthread  pthrea.c

【线程安全和可重入】

线程安全:概念比较直观。一般说来,一个函数被称为线程安全的,当且仅当被多个并发线程反复调用时,它会一直产生正确的结果。

线程安全的条件:
要确保函数线程安全,主要需要考虑的是线程之间的共享变量。属于同一进程的不同线程会共享进程内存空间中的全局区和堆,而私有的线程空间则主要包括栈和寄存器。因此,对于同一进程的不同线程来说,每个线程的局部变量都是私有的,而全局变量、局部静态变量、分配于堆的变量都是共享的。在对这些共享变量进行访问时,如果要保证线程安全,则必须通过加锁的方式。

可重入:概念基本没有比较正式的完整解释,但是它比线程安全要求更严格。根据经验,所谓“重入”,常见的情况是,程序执行到某个函数foo()时,收到信号,于是暂停目前正在执行的函数,转到信号处理函数,而这个信号处理函数的执行过程中,又恰恰也会进入到刚刚执行的函数foo(),这样便发生了所谓的重入。此时如果foo()能够正确的运行,而且处理完成后,之前暂停的foo()也能够正确运行,则说明它是可重入的。

可重入的判断条件:
要确保函数可重入,需满足以下几个条件:
1、不在函数内部使用静态或全局数据 
2、不返回静态或全局数据,所有数据都由函数的调用者提供。 
3、使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。
4、不调用不可重入函数。

可重入与线程安全并不等同,一般说来,可重入的函数一定是线程安全的,但反过来不一定成立。


【进程/线程间同步】

并发,是在同一个cpu上同时(不是真正的同时,而是看来是同时,因为cpu要在多个程序间切换)运行多个程序。

并行,是每个cpu运行一个程序。

同步,是协同步调,按预定的先后次序进行运行。

互斥,两个或两个以上的进程,不能同时进入关于同一组共享变量的临界区域,否则可能发生与时间有关的错误,这种现象被称作进程互斥.

把异步环境下的一组并发进程因直接制约而互相发送消息、进行互相合作、互相等待,使得各进程按一定的速度执行的过程称为进程间的同步。

1.  临界区(Critical Section)-- 同一个进程内实现互斥

通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问临界区,那么在有一个线程进入后,其他试图访问此临界区的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。

2. 互斥量(Mutex)-- 可以跨进程实现互斥

采用互斥对象机制。 只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享 。

3. 信号量(Semaphore)--主要是实现同步,可以跨进程

信号量允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时,则说明当前占用资源的线程数已经达到了所允许的最大数目,不能再允许其他线程的进入,此时的信号量信号将无法发出。

PV操作及信号量的概念都是由荷兰科学家E.W.Dijkstra提出的。信号量S是一个整数,S大于等于零时代表可供并发进程使用的资源实体数,但S小于零时则表示正在等待使用共享资源的进程数。

P操作申请资源:

(1)S减1;

(2)若S减1后仍大于等于零,则进程继续执行;

(3)若S减1后小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转入进程调度。  

V操作释放资源:

(1)S加1;

(2)若相加结果大于零,则进程继续执行;

(3)若相加结果小于等于零,则从该信号的等待队列中唤醒一个等待进程,然后再返回原进程继续执行或转入进程调度。

4. 事件(Event)-- 实现同步,可以跨进程

用来通知线程有一些事件已发生,从而启动后继任务的开始。

 

总结:

1.互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。互斥量比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。

2. 通过互斥量可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可的数据库系统,可以根据用户购买的访问许可数量来决定有多少个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号灯对象可以说是一种资源计数器。

【进程间通信方式】

进程间通信就是在不同进程之间传播或交换信息。

进程的用户空间是互相独立的,一般而言是不能互相访问的,唯一的例外是共享内存区。但是,系统空间却是“公共场所”,所以内核显然可以提供这样的条件。除此以外,那就是双方都可以访问的外设了。在这个意义上,两个进程当然也可以通过磁盘上的普通文件交换信息,或者通过“注册表”或其它数据库中的某些表项和记录交换信息。广义上这也是进程间通信的手段,但是一般都不把这算作“进程间通信”。因为那些通信手段的效率太低了,而人们对进程间通信的要求是要有一定的实时性。

进程间通信主要包括管道,系统IPC(包括消息队列,信号量,共享内存),SOCKET.

1. 管道

管道是一种半双工的通信方式,数据只能单向流动,一端读一端取,当一端分配到读任务后,那么他就固定了,不能再担当写的角色了。

管道只能承载无格式字节流,缓冲区大小受限。管道的大小,即为sizeof(readbuffer),写入比该数字大的数据时,先只会写sizeof(readbuffer)个数据到管道,然后写端阻塞,等待读端取走数据,然后按同样的规则写入剩余的部分,这个也体现了写入操作的非原子性。写请求字节数还有一个最大阀值,在/usr/include/linux有文件 limits.h中宏定义
            #define PATH_MAX        4096
            此时,假如指明的管道大小大于PATH_MAX ,系统会按这个PATH_MAX 作为写请求最大字节数。

管道分为命名管道和匿名管道,匿名管道只能用于父子进程之间的通信,而命名管道则可用于非父子进程之间。命名管道就是FIFO,管道是先进先出的通讯方式 。

2. 消息队列

消息队列用于运行于同一台机器上的进程间通信,与管道相似;首先在一个进程中创建一个消息队列,然后再往消息队列中写数据,而另一个进程则从那个消息队列中取数据。

需要注意的是,消息队列是用创建文件的方式建立的,如果一个进程向某个消息队列中写入了数据之后,另一个进程并没有取出数据,即使向消息队列中写数据的进程已经结束,保存在消息队列中的数据并没有消失,也就是说下次再从这个消息队列读数据的时候,就是上次的数据!!!!  

消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

3. 信号量

本质上,信号量是一个计数器,它用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。一般说来,为了获得共享资源,进程需要执行下列操作:

(1)测试控制该资源的信号量;

(2)若此信号量的值为正,则允许进程使用该资源,进程将进号量减1;

(3)若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1);

(4)当进程不再使用一个信号量控制的资源时,信号量值加1,如果此时有进程正在睡眠等待此信号量,则唤醒此进程。

4. 共享内存

共享内存通常由一个进程创建,其余进程对这块内存区进行读写。

共享内存是最快的 IPC 方式,往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

得到共享内存有两种方式:映射/dev/mem设备和内存映像文件。前一种方式不给系统带来额外的开销,但在现实中并不常用,因为它控制存取的是实际的物理内存;  

5. 套接字

套接字通信并不为Linux所专有,在所有提供了TCP/IP协议栈的操作系统中几乎都提供了socket,而所有这样操作系统,对套接字的编程方法几乎是完全一样的

6. 信号

信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

信号可以在任何时候发送给某一进程,而无须知道该进程的状态。如果该进程并未处于执行状态,则该信号就由内核保存起来,直到该进程恢复执行并传递给他为止。如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

以上几种方式的比较:    

1. 匿名管道:速度慢,容量有限,只有父子进程能通讯    

2. FIFO:任何进程间都能通讯,但速度慢    

3. 消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题    

4. 信号量:不能传递复杂消息,只能用来同步    

5.共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存

 


 【参考】

1. http://blog.csdn.net/yaosiming2011/article/details/44280797

2. http://blog.csdn.net/hairetz/article/details/4281931/

3. http://blog.csdn.net/yang1994/article/details/7732116

4. http://www.cnblogs.com/LUO77/p/5816326.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值