进程与线程

目录:
    1、进程管理
         (1)进程的三种基本状态
            (2)临界资源、临界区
            (3)进程同步原则
            (4)进程同步方式
            (5)进程通信方式
    2、线程管理
            (1)线程的基本操作
            (2)线程同步方式
            (3)线程通信方式
    3、进程与线程的区别
    4、进程池、线程池


1、进程管理
1.1 进程的三种基本状态
就绪(Ready)状态
    进程已经分配到除cpu之外的所有必要资源,只要获得cpu,便可立即执行;一个系统中处于就绪状态的进程可能有多个,通常将他们排成一个队列,称为就绪队列;
执行状态
    进程已经获得cpu,其程序正在执行。单处理机系统中,只有一个进程处于执行状态,多处理机系统中,则有多个进程处于执行状态
阻塞状态
    正在执行的进程由于“请求io,申请缓冲空间”等,无法继续执行,便放弃处理机处于阻塞状态
1.2 进程同步
    一组在异步环境下的并发进程,各自的执行结果互为对方的执行条件,从而限制各进程的执行速度的过程称为并发进程之间的直接制约。把异步环境下的一组并发进程,因直接制约而相互发送消息而进行相互合作、相互等待,使得各个进程按一定速度执行的过程称为进程间的同步。(计算机操作系统教程,张尧学)      
    进程同步的主要任务是对多个相关进程在执行次序上进行协调,以使并发执行的各个进程之间能有效的共享资源和相互合作,从而使程序的执行具有可再现性。(计算机操作系统,汤子瀛)这个定义更好一些。
(1)两种制约关系
    a,间接制约
        处于同一系统中的进程,共享某种系统资源,比如打印机,所谓简介相互制约即源于这种资源共享;
    b,直接制约
        这种制约主要源于进程间的合作,例如,一个输入进程A通过单缓冲向进程B提供数据,当缓冲为空时,B会阻塞,当A把数据放入缓冲区时,将B唤醒;反之,当缓冲区已经满时,A阻塞,当B将缓冲区数据取走后便可以唤醒A;
 
(2)临界资源,临界区
 link:http://baike.baidu.com/view/1237110.htm
        不论是硬件临界资源,还是软件临界资源,多个进程必须互斥地对它进行访问。每个进程中访问临界资源的那段代码称为临界区(Critical Section)(临界资源是一次仅允许一个进程使用的共享资源,比如打印机、磁带机等)。每次只准许一个进程进入临界区,进入后不允许其他进程进入。不论是硬件临界资源,还是软件临界资源,多个进程必须互斥地对它进行访问。
        无论是硬件临界资源,还是软件临界资源,多个进程必须互斥的对它进行访问,人们把在每个进程中访问临界资源的那段代码称为临界区,显然,如果能保证并发进程互斥的进入自己的临界区,并可以实现诸进程对临界资源的互斥访问;因此,每个进程在进入临界区之前,应先对欲访问的临界资源进行检查,看它是否正被访问,如果此刻临界资源未被访问,进程便可以进入临界区对此资源进行访问,并设置它正被访问的标志;如果该临界资源正被某个进程访问,则本进程不能进入临界区。
 
(3)同步机制应遵循的原则
    为了实现进程互斥的进入自己的临界区,可以用软件方法,更多的是在系统中设置专门的同步机构来协调各进程的运行,所有的同步机制都应遵循以下四条准则:
    空闲让进:当无进程处于临界区时,应允许一个进程进入临界区;
    忙则等待:当已有进程进入临界区时,其他进程则等待;
    有限等待:对要求访问临街资源的进程,应保证在有限时间内进入自己的临界区;避免“死等”;
    让权等待:当进程不能进入自己的临界区时,应立即释放处理机,以免进程陷入“忙等”状态;
(4)同步方式
(a)信号量
    信号量是一种卓有成效的进程同步工具,有整型信号量、记录型信号量、AND信号量、信号量集
(b)管程机制(条件变量)
 

进程间同步总结(linux windows)  

            windows进程间同步方式有:1. 互斥量 mutex 2. 信号量 semaphore 3.事件 event 4.临界区 Critical Section 5.互锁函数

            临界区和互锁函数没有相应的内核对象因而不能跨进程

            linux进程同步方式有:互斥量、读写锁、条件变量
            linux内核同步方法:原子操作、自旋锁、读-写自旋锁、信号量、读-写信号量、完成变量、BKL、禁止抢占(preemp_disable()preemp_enable())
(5)进程通信方式
  • 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;
  • 消息队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  • 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
  • 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
  • 套接字(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。


2、线程管理
(1)线程基本操作
a、创建线程
    int pthread_create(pthread_t *thread, pthread_attr_t*attr, void *(*start_routine)(void *), void *arg);
        //第一个参数是指向pthread_t类型数据的指针,第二个参数设置线程属性,一般为NULL
        //第三个参数表示线程将要启动执行的函数,第四个参数表示传递给该函数的参数
        //成功返回0,失败返回错误代码
b、向线程体传递参数
        pthread_create函数在创建线程时由内核向新线程传递参数,如果需要多个参数,则将参数组织在一个结构体内,再将结构体的地址作为参数传给新线程。
c、线程访问资源的限制
    新线程和进程公用文件描述符、文件对象、数据段、堆和栈,因此在新线程中对这些资源的使用会影响到主线程。
d、终止线程
    void pthread_exit(void *retval)
        //线程调用此函数终止执行
        //返回一个指向某个对象的指针,注意,绝对不能用它来返回一个指向局部变量的指针,因为线程调用该函数后,这个局部变量    
        //就不存在了,
e、取消一个线程的运行
    一个线程可以被另一个线程取消掉,linux下使用
    int pthread_cancel(pthread_t tid);
        //参数表示要取消线程的线程ID,成功返回0,失败返回错误编号
    pthread_exit(PTHREAD_CANCELED);
        //调用pthread_cancel函数等效于要被取消的线程自己调用
f、线程退出函数
    一个线程在退出时也可以调用用户设置好的函数,这些函数成为线程清理程序,记录在栈中,linux中使用pthread_cleanup_push函数添加一个清理程序记录,使用pthread_cleanup_pop函数调用清理程序。
    void pthread_cleanup_push(void (*rtn)(void *), void *arg);
        //第一个参数是一个函数指针,指向清理程序;第二个参数表示清理程序的参数。
    void pthread_cleanup_pop(int execte);
        //表示是否执行栈顶的清理程序,参数为0时表示不执行清理程序,参数不为0时表示执行栈顶清理程序
    pthread_cleanup_push会在3中情况下执行:
        ()调用pthread_exit函数时
        ()线程被其他线程取消时
        ()使用非零参数调用pthread_cleanup_pop时。
 
(2)线程同步方式
    当多个线程共享相同的内存时,需要确保每个线程看到一致的数据视图,如果每个线程使用的变量都是其他线程不会读取或修改的,那不不存在一致性问题,同样的,如果变量是只读的,多个线程同时读取变量也不会有一致性问题,但是当某个线程可以修改变量,而其他线程也可以读取或者修改这个变量时,就需要对这些线程进行同步,确保他们访问变量时不会读到无效的值。(UNIX环境高级编程)
    由于线程共享进程资源,因此在多线程并发执行的环境中就有可能出现冲突操作的情况
    所谓线程同步是指线程之间在相互通信时避免破坏各自数据的能力。
    线程同步主要方式有信号量互斥量、读写锁、条件变量。
参考:
(1)Linux程序设计p423
(2)Linux C 程序设计大全 p480
 
(a)、信号量 #include<semaphore.h>

如同进程一样,线程也可以通过信号量来实现通信,虽然是轻量级的。信号量函数的名字都以"sem_"打头。线程使用的基本信号量函数有四个。

  1. 信号量初始化。
    int sem_init (sem_t *sem , int pshared, unsigned int value);
    这是对由sem指定的信号量进行初始化,设置好它的共享选项(linux 只支持为0,即表示它是当前进程的局部信号量),然后给它一个初始值VALUE。
  2. 等待信号量。给信号量减1,然后等待直到信号量的值大于0。
    int sem_wait(sem_t *sem);
  3. 释放信号量。信号量值加1。并通知其他等待线程。
    int sem_post(sem_t *sem);
  4. 销毁信号量。我们用完信号量后都它进行清理。归还占有的一切资源。
    int sem_destroy(sem_t *sem);
(b)、互斥量 #include<pthread.h>
    互斥量作为一种锁,任何想要进入临界区的线程都要对获得锁,如果该锁已经被某一个线程所持有,则测试线程会阻塞,直到该锁被释放;Linux下用pthread_mutex_t数据类型表示互斥量, 
 
初始化:
    pthread_mutex_init(pthread_mutex_t *restrict, const pthread_mutexattr*restrict attr); 
        //第一个参数是互斥量的指针,第二个参数是互斥量的属性,成功返回0,失败返回错误号
    还有一种初始化方法,
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
        //使用系统已经定义好的互斥量
销毁:   
    pthread_mutex_destroy(pthread_mutex_t *mutex);
        // 参数表示要销毁的互斥量,成功返回0,失败返回错误号
    
互斥量是一种对用户透明的数据结构,用户不可以直接对其进行操作,而应该使用系统提供的互斥量的函数接口;
 
加锁
    int pthread_mutex_lock(pthread_mutex_t *mutex);
        //获取互斥量的锁
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
        //获取互斥量的锁
    二者的区别在于前者如果没有得到锁,会阻塞; 后者会返回一个错误编号EBUSY,表示该锁处于繁忙状态;
释放锁
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
        //成功返回0,失败返回错误号
 
(c)、读写锁 #include<pthread.h>
    读写锁与互斥量类似,但是并行性更高,其原因在于互斥锁每次只有一个线程可以得到锁进行操作,其余的线程都因为得不到锁而处于阻塞状态。创建多线程的本意是为了并发执行任务,但是由于互斥锁的缘故导致线程的操作变成了串行的,由此可知程序的运行效率会大大折扣。
    在程序执行中,如果对共享资源做读操作的线程远远大于写线程的时候,使用读写锁可以大大提高线程的并发度,从而提高程序运行的效率。
    linux下用pthread_rwlock_t结构类型来表示读写锁,在使用读写锁之前要对其进行初始化
初始化:
    int pthread_rwlock_init(pthread_rwlock * restrict rwlock, const pthread_rwlockattr_t * restrict attr)
        //第一个参数是读写锁的指针;第二个参数是读写锁的属性,通常为NULL,成功返回0
销毁:
    int pthread_rwlock_destory(pthread_rwlock_t *rwlock);
        //参数表示需要销毁的读写锁,成功返回0,失败返回错误号
 
读模式下获取读写锁:
    int pthread_rwlock_rdlock(pthread_rwlock_t * rwlock);                       (1)
    int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);                      (2)
        //参数表示一个读写锁,
        //如果该读写锁已经被某一个线程在读模式下得到,则测试线程仍然会得到该锁;
        //如果该读写锁已经被某一个线程在写模式下得到,或者有一个线程在写模式下等待该锁,这时(1)会阻塞;(2)会返回EBUZY;
        //成功返回0, 失败返回错误号
写模式获取读写锁:
    int pthread_rwlock_wrlock(pthread_rwlock_t * rwlock);                        (1)
    int pthread_rwlock_trywrlock(pthread_rwlock_t * rwlock);                      (2)
        //如果在测试读写锁的时候有任意一个线程占有该锁,(1)会阻塞,并且会导致其后所有申请该读写锁的线程(包括读线程和写线程)阻塞等待,
        //(2)会返回EBUZY
 
释放读写锁:
    无论线程在哪种模式下占有锁,都使用  释放读写锁
    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
        //成功返回0,失败返回错误号
 
(d)、条件变量 #include<pthread.h>

互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。条件变量分为两部分: 条件和变量。条件本身是由互斥量保护的。线程在改变条件状态前先要锁住互斥量。条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。

  1. 初始化条件变量。
    静态态初始化,pthread_cond_t cond = PTHREAD_COND_INITIALIER;
    动态初始化,int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
  2. 等待条件成立。释放锁,同时阻塞等待条件变量为真才行。timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait)
    int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
    int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
  3. 激活条件变量。pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)
    int pthread_cond_signal(pthread_cond_t *cond);
    int pthread_cond_broadcast(pthread_cond_t *cond); //解除所有线程的阻塞
  4. 清除条件变量。无线程等待,否则返回EBUSY
    int pthread_cond_destroy(pthread_cond_t *cond);
 (3)线程通信方式
线程间的通信目的主要是用于线程同步。所以线程没有像进程通信中的用于数据交换的通信机制。
所以线程通信方式:互斥量、读写锁、


 
3、进程与线程的区别

link:http://s.sousb.com/2011/04/12/%e8%bf%9b%e7%a8%8b%e4%b8%8e%e7%ba%bf%e7%a8%8b/

  • 通常一个进程都拥有若干个线程,至少也有一个线程;
  • 1)调度:进程是操作系统资源(包括独立的虚拟地址空间,拥有的资源如打开的文件,内存空间,I/O设备等)分配的基本单位,而线程是操作系统任务调度的基本单位,拥有的资源相对进程来说较少,只有运行所必须的堆栈、寄存器等。
  • 2)地址空间:进程有独立的地址空间,线程没有独立的地址空间 只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),与同属一个进程的其他的线程共享进程所拥有的全部资源(全局数据、堆、栈、打开的文件、内存、I/O设备等);.
  • 3)系统开销:(a)在创建和撤销进程,系统都要为之创建和回收进程控制块,分配和回收资源,如内存空间和I/O设备等,操作系统所付出的开销明显大于线程创建或撤销时的开销。类似的,进程切换,涉及到当前CPU环境的保存以及新被调度运行进程的CPU环境的设置; 而线程的切换仅需要保存和设置少量寄存器的内容,不涉及存储器管理方面的操作,所以切换代价也比较小;(b)同步和通信:由于一个进程中的多个线程具有相同的地址空间,在同步和通信的实现方面线程也比较容易
  • 线程之间的通信简单(共享内存即可,但须注意互斥访问的问题),而不同进程之间的通信更为复杂,通常需要调用内核实现,主要由以下几种:
    • 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;
    • 消息队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
    • 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
    • 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
    • 套接字(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。
多进程、多线程:
            用多进程时每个进程都有自己的地址空间,线程则共享地址空间。(所有其他区别都是由此而来)

 

  • 速度:线程产生的速度快,线程间的通信快、切换快,因为他们在同一个地址空间内。
  • 资源利用率:线程的资源利用率比较好也是因为他们在同一地址空间内。
  • 同步问题: 线程使用公共变量或内存是需要使用同步机制,还是因为他们在同一地址空间内。
什么是线程安全?线程安全是怎么完成的(原理)? 

线程安全就是说多线程访问同一代码,不会产生不确定的结果。编写线程安全的代码是低依靠线程同步。

 

 

在多线程环境中,当各线程不共享数据的时候,那么一定是线程安全的。问题是这种情况并不多见,在多数情况下需要共享数据,这时就需要进行适当的同步控制了。

线程安全一般都涉及到synchronized 就是一段代码同时只能有一个线程来操作 ,不然中间过程可能会产生不可预制的结果

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。  

 



4、进程池、线程池

(1)线程和进程的选择 link:http://blog.csdn.net/wuxunfeng/article/details/5286540       

(a)线程与进程的选择:

        当连接之间的交互比较多,采用多线程更好;当网络连接之间不需要很多交互时,应选择进程;

        使用I/O多路复用,让单个进程可以处理多个连接,如果进程池只有一个进程, 同一个服务进程里的连接之间可以很方便的通讯,但是如果是多个子进程,那么子进程之间就不能直接通讯,通常要要消息队列、共享内存、管道等。当服务端需要处理连接之间的交互,而且性能上需要多个进程,那么使用线程池代替进程池应该是一种比较好的方式。线程和进程相比较,线程的优点是性能开销更小,因为在同一个进程空间里,线程之间的通讯很容易;因为线程共享进程空间数据,因此线程在处理的时候比进程更容易出错,线程池的方式简化了通讯方式,但是为了线程安全,这方面的复杂性就增加了。所以如果网络连接之间并不需要很多交互,每个连接处理都是独立的,那么应该选择进程。这个原则不但对网络处理,对于别的处理也是如此。

 

(b)多进程与进程池的选择:处理短连接,一般用进程池,处理长连接一般用多进程,方便一些

    这里说的进程池和多进程是,进程池是预先启动多个子进程,并且可以管理进程;多进程是指到主进程阻塞于ACCEPT处理连接请求,由子进程单独负责每个套接口连接。

    处理短连接连接,因为客户端频繁的连接服务器和断开连接,服务端的主要性能开销应该是在进程切换上,基于性能考虑采用进程池的方式会比多进程好.如果连接并发量不大,没有性能上的问题,多进程的程序会比进程池简单很多。

    如果服务端处理的是长连接。如果让进程池或者多进程中一个子进程只处理一个连接,系统的主要性能开销主要取决于进程的数量,在进程的数量到一定数量的时候,会对服务器造成比较大的压力。所以在处理长连接时,一般才用I/O多路复用的方式,LINUX上I/O多路复用,有SELECT、EPOLL等。

    使用I/O多路复用一方面可以让单个进程同时多个连接,可以提高并发连接数。另一方面,还可以可以让CLIENT和SERVER的通讯更加灵活,例如使用I/O多路复用,让CLIENT和SERVER可以很容易的异步通讯。 

 

(2)进程池
    进程池技术的应该致少由以下两部分组成link:http://baike.baidu.com/view/4429048.htm
管理进程
管理进程负责创建资源进程,把工作交给空闲资源进程处理,回收已经处理完工作的资源进程。
资源进程
预先创建好的空闲进程,管理进程会把工作分发到空闲进程来处理。
上面资源进程跟管理进程的概念很好理解,下面就是进程池的关键,管理进程如何有效的管理资源进程,分配任务给资源进程,回收空闲资源进程,管理进程要有效的管理资源进程,那么管理进程跟资源进程间必然需要交互,通过IPC,信号,信号量消息队列,管道等进行交互。
使用范围
采用进程池节省了产生新进程,杀死进程的过程中分配资源回收资源的时间,但同时也占用了相当的内存资源。(以空间换时间)
如果你的系统很忙,业务很频繁,频繁的开进程,杀进程,那建议用进程池。开多大还要看你自己机器的具体情况,多测试得到最佳效果。

 

一个非常简单的http服务器,该服务器只能接收Get请求。 link:http://blog.chinaunix.net/uid-20481436-id-1941519.html
流程大概如下:
1,父进程listen,创建pipe(下面所有父子进程之间的通信都用该pipe)
2,父进程预fork n个子进程
3,各个子进程accept(listenfd),即所有子进程竞争accept请求。由于listenfd是在fork之前就有的,所以所有子进程都可以访问到,不需用到“进程间文件描述符传递”问题;
4,子进程每accept到一个请求都告诉父进程,父进程把请求数加1;子进程没完成一个请求,父进程把请求数减1;当父进程发现请求数 >= 子进程数时,父进程创建新的子进程,并把子进程数加1(当然子进程数有个预先上限);当父进程发现子进程数大于请求数加1时,父进程杀死多余的子进程。
总的来说,思想是让子进程accept并处理请求,父进程通过子进程发来的信息控制请求数与子进程数之间的关系。

SPProcPool 是一个 linux/unix 平台上的进程池服务器框架,使用 c++ 实现。link:http://www.iteye.com/topic/147010

Erlang进程池:http://blog.csdn.net/thomescai/article/details/8212758

 

(3)线程池

 

开源实现:threadpool线程池

线程池的原理:   link:  http://www.cnblogs.com/zping/archive/2008/10/29/1322440.html

      来看一下线程池究竟是怎么一回事?其实线程池的原理很简单,类似于操作系统中的缓冲区的概念,它的流程如下:先启动若干数量的线程,并让这些线程都处于睡眠状态,当客户端有一个新请求时,就会唤醒线程池中的某一个睡眠线程,让它来处理客户端的这个请求,当处理完这个请求后,线程又处于睡眠状态。可能你也许会问:为什么要搞得这么麻烦,如果每当客户端有新的请求时,我就创建一个新的线程不就完了?这也许是个不错的方法,因为它能使得你编写代码相对容易一些,但你却忽略了一个重要的问题??性能!就拿我所在的单位来说,我的单位是一个省级数据大集中的银行网络中心,高峰期每秒的客户端请求并发数超过100,如果为每个客户端请求创建一个新线程的话,那耗费的CPU时间和内存将是惊人的,如果采用一个拥有200个线程的线程池,那将会节约大量的的系统资源,使得更多的CPU时间和内存用来处理实际的商业应用,而不是频繁的线程创建与销毁。

 

link:http://www.ibm.com/developerworks/cn/java/l-threadPool/

 

一般一个简单线程池至少包含下列组成部分。

  1. 线程池管理器(ThreadPoolManager):用于创建并管理线程池
  2. 工作线程(WorkThread): 线程池中线程
  3. 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行。
  4. 任务队列:用于存放没有处理的任务。提供一种缓冲机制。

 

线程池管理器至少有下列功能:创建线程池,销毁线程池,添加新任务 创建线程池的部分代码如下:

  …
        //create threads
        synchronized(workThreadVector)
        {
            for(int j = 0; j < i; j++)
            {
                threadNum++;
               WorkThread workThread = new WorkThread(taskVector, threadNum);
                workThreadVector.addElement(workThread);
            }
        }
…

注意同步workThreadVector并没有降低效率,相反提高了效率,请参考Brian Goetz的文章。 销毁线程池的部分代码如下:

  …
        while(!workThreadVector.isEmpty())
        {
        if(debugLevel > 2)
         System.out.println("stop:"+(i));
         i++;
            try
            {
                WorkThread workThread = (WorkThread)workThreadVector.remove(0);
                workThread.closeThread();
                continue;
            }
            catch(Exception exception)
            {
                if(debugLevel > 2)
                    exception.printStackTrace();
            }
            break;
        }
   …
   

添加新任务的部分代码如下:

   …
        synchronized(taskVector)
        {
            taskVector.addElement(taskObj);
            taskVector.notifyAll();
        }
   …
   

工作线程是一个可以循环执行任务的线程,在没有任务时将等待。由于代码比较多在此不罗列.

任务接口是为所有任务提供统一的接口,以便工作线程处理。任务接口主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等。在文章结尾有相关代码的下载。

        线程池尺寸对于大量任务处理的效率有非常明显的提高,但是一旦尺寸选择不合理(过大或过小)就会严重降低影响服务器性能。理论上"过小"将出现任务不能及时处理的情况,但在图表中显示出某些小尺寸的线程池表现很好,这是因为测试驱动中有很多线程同步开销,且这个开销相对于完成单个任务的时间是不能忽略的。"过大"则会出现线程间同步开销太大的问题,而且在线程间切换很耗CPU时间,在图表显示的很清楚。可见任何一个好技术,如果滥用都会造成灾难性后果。

深入研究线程池http://blog.csdn.net/axman/article/details/1481197

posted @ 2013-09-16 10:01  lpshou  阅读(1315)  评论(0编辑  收藏  举报