进程、线程、协程区别

 现在多进程多线程已经是老生常谈了,协程也在最近几年流行起来。python中有协程库gevent,python web框架tornado中也用了gevent封装好的协程。本文主要介绍进程、线程和协程三者之间的区别。
 总线程数<= CPU数量:并行运行
 总线程数> CPU数量:并发运行
一、概念

1、进程
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。
代码:
(1)使用Runtime的exec()方法
(2)使用ProcessBuilder的start()方法
(3)子进程可以通过使用使用ProcessBuilder的start()方法和Runtime的exec()方法

package jiademo;

import java.io.BufferedReader;  
import java.io.File;  
import java.io.InputStreamReader;  

public class Test1 {  
public static void main(String[] args) {  
   try {  
    Process p = null;  
    String line = null;  
    BufferedReader stdout = null;  

    //list the files and directorys under C:\  
    p = Runtime.getRuntime().exec("CMD.exe /C dir", null, new File("C:\\"));  
    stdout = new BufferedReader(new InputStreamReader(p  
      .getInputStream()));  
    while ((line = stdout.readLine()) != null) {  
     System.out.println(line);  
    }  
    stdout.close();  

    //echo the value of NAME  
    p = Runtime.getRuntime().exec("CMD.exe /C echo %NAME%", new String[] {"NAME=TEST"});     
    stdout = new BufferedReader(new InputStreamReader(p  
      .getInputStream()));  
    while ((line = stdout.readLine()) != null) {  
     System.out.println(line);  
    }  
    stdout.close();  
   } catch (Exception e) {  
    e.printStackTrace();  
    }  
  }
}
package jiademo;

import java.io.BufferedReader;  
import java.io.File;  
import java.io.InputStreamReader;  
import java.util.ArrayList;  
import java.util.List;  

public class Test2 {  
public static void main(String[] args) {  
   try {  
    List list = new ArrayList();  
    ProcessBuilder pb = null;  
    Process p = null;  
    String line = null;  
    BufferedReader stdout = null;  

    //list the files and directorys under C:\  
    list.add("CMD.EXE");  
    list.add("/C");  
    list.add("dir");  
    pb = new ProcessBuilder(list);  
    pb.directory(new File("C:\\"));  
    p = pb.start();  

    stdout = new BufferedReader(new InputStreamReader(p  
      .getInputStream()));  
    while ((line = stdout.readLine()) != null) {  
     System.out.println(line);  
    }  
    stdout.close();  

    //echo the value of NAME  
    pb = new ProcessBuilder();  
    pb.command(new String[] {"CMD.exe", "/C", "echo %NAME%"});  
    pb.environment().put("NAME", "TEST");  
    p = pb.start();  

    stdout = new BufferedReader(new InputStreamReader(p  
      .getInputStream()));  
    while ((line = stdout.readLine()) != null) {  
     System.out.println(line);  
    }  
    stdout.close();  
   } catch (Exception e) {  
    e.printStackTrace();  
   }  
  }  
}

2、线程
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。
代码:
(1)继承Thread类
(2)实现Runnable接口
(3)使用ExecutorService、Callable、Future实现有返回结果的多线程

    public class MyThread extends Thread {  
      public void run() {  
       System.out.println("MyThread.run()");  
      }  
    }  
    MyThread myThread1 = new MyThread();  
    MyThread myThread2 = new MyThread();  
    myThread1.start();  
    myThread2.start();  
    public class MyThread extends OtherClass implements Runnable {  
      public void run() {  
       System.out.println("MyThread.run()");  
      }  
    }  
//Executors类,提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。
public static ExecutorService newFixedThreadPool(int nThreads)
//创建固定数目线程的线程池。
public static ExecutorService newCachedThreadPool()
//创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用//的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
public static ExecutorService newSingleThreadExecutor()
//创建一个单线程化的Executor。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
//创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类

3、协程
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
代码:
(1)实现Coroutine接口,重写run方法,在run方法中可以在任意位置调用coutinuation.suspend()方法暂停语句的执行并保存上下文
(2)协程的运行通过CoroutineRunner对象来实现的。创建一个CoroutineRunner对象,这个对象需要传入一个刚才实现的Coroutine接口类进行构造
(3)调用coroutineRunner.execute()方法,将会调用Coroutine的run方法
(4)一般会用到kilim( https://github.com/kilim/kilim )这个框架。但是看了看其用法,比较难懂。跟之前python所理解的协程的使用有很大的区别。所以就上github看看有没有别的协程框架可以用。然后发现了这个框架( https://github.com/offbynull/coroutines),也就是下面实现协程所用的框架
(5)Akka是开源的,可以通过Apache 2许可获得。可以从 http://akka.io/downloads/ 下载。Actors为你提供:对并发/并行程序的简单的、高级别的抽象。异步、非阻塞、高性能的事件驱动编程模型。
非常轻量的事件驱动处理(1G内存可容纳约270万个actors)

import akka.actor.UntypedActor;

public class TestActor extends UntypedActor {
    public void onReceive(Object msg) {
        getContext().reply(msg + " World");
    }
}
import akka.actor.Actors;
import akka.remoteinterface.RemoteServerModule;

public class Server {

    public static void main(String[] args) {
        RemoteServerModule server = Actors.remote().start("localhost", 8888);
        server.register("test-service", Actors.actorOf(TestActor .class));
    }
}
import akka.actor.ActorRef;
import akka.actor.Actors;

public class Client {

    public static void main(String[] args) {

        ActorRef actor = Actors.remote().actorFor("test-service", "localhost", 8888);
        for (int i = 0; i < 10000; i++) {
            Object result = actor.sendRequestReply("Hello");
            System.out.println(result);
        }
    }
} 

二、区别:
1、进程多与线程比较
线程是指进程内的一个执行单元,也是进程内的可调度实体。线程与进程的区别:
1) 地址空间:线程是进程内的一个执行单元,进程内至少有一个线程,它们共享进程的地址空间,而进程有自己独立的地址空间
2) 资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
3) 线程是处理器调度的基本单位,但进程不是
4) 二者均可并发执行
5) 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制
2、协程多与线程进行比较
1) 一个线程可以多个协程,一个进程也可以单独拥有多个协程,这样python中则能使用多核CPU。
2) 线程进程都是同步机制,而协程则是异步
3) 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态
三、进程和线程、协程在python中的使用
  1、多进程一般使用multiprocessing库,来利用多核CPU,主要是用在CPU密集型的程序上,当然生产者消费者这种也可以使用。多进程的优势就是一个子进程崩溃并不会影响其他子进程和主进程的运行,但缺点就是不能一次性启动太多进程,会严重影响系统的资源调度,特别是CPU使用率和负载。使用多进程可以查看文章《python 多进程使用总结》。注:python2的进程池在类中的使用会有问题,需要把类函数定义成全局函数。具体可参考 http://bbs.chinaunix.net/thread-4111379-1-1.html
  2、多线程一般是使用threading库,完成一些IO密集型并发操作。多线程的优势是切换快,资源消耗低,但一个线程挂掉则会影响到所有线程,所以不够稳定。现实中使用线程池的场景会比较多,具体可参考《python线程池实现》。
  3、协程一般是使用gevent库,当然这个库用起来比较麻烦,所以使用的并不是很多。相反,协程在tornado的运用就多得多了,使用协程让tornado做到单线程异步,据说还能解决C10K的问题。所以协程使用的地方最多的是在web应用上。
总结一下就是IO密集型一般使用多线程或者多进程,CPU密集型一般使用多进程,强调非阻塞异步并发的一般都是使用协程,当然有时候也是需要多进程线程池结合的,或者是其他组合方式。

你可能感兴趣的:(JAVA开发,服务器运维)