注意:
- 多线程可以提高CPU利用率,不能提高内存利用率
活动就绪------suspend------>静止就绪
活动就绪<------active------静止就绪
活动阻塞------suspend------>静止阻塞
活动阻塞<------active------静止阻塞
运行-->就绪 原因:时间片用完/被抢占(优先级等)
进程挂起的原因有:
1、终端用户的请求
2、父进程的请求
3、负荷调节的需要
4、操作系统的需要
进程出现故障将进入终止状态
-
wait()、notify()和notifyAll():Object类中的方法
从这三个方法的文字描述可以知道以下几点信息:
1)wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。
2)调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)
3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;
4)调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程;
有朋友可能会有疑问:为何这三个不是Thread类声明中的方法,而是Object类中声明的方法(当然由于Thread类继承了Object类,所以Thread也可以调用者三个方法)?其实这个问题很简单,由于每个对象都拥有monitor(即锁),所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作了。而不是用当前线程来操作,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。 - Condition是个接口,基本的方法就是await()和signal()方法;(Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition。)
Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用Conditon中的await()对应Object的wait(); Condition中的signal()对应Object的notify(); Condition中的signalAll()对应Object的notifyAll() - ThreadLocal类用于创建一个线程本地变量
ThreadLocal存放的值是线程封闭,线程间互斥的,主要用于线程内共享一些数据,避免通过参数来传递
线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收
在Thread类中有一个Map,用于存储每一个线程的变量的副本。
对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式 - sleep()
sleep()是Thread类中的方法,而wait()则是Object类中的方法。sleep()方法导致了程序暂停,但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。 wait()方法会导致线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
注意是准备获取对象锁进入运行状态,而不是立即获得
创建进程:
- 申请空白PCB(进程控制块);
- 为新进程分派资源;
- 初始化PCB;
- 将新进程插入就绪队列;
进程控制块PCB(Process Control Block)
注意:
Unix操作系统的进程控制块中常驻内存的是proc
Linux创建进程的时候,同时创建进程控制块和进程对应的堆栈。该堆栈分为用户栈和系统栈,也就是核心栈。用户栈在用户态使用,用来保存局部变量,函数参数等,而系统栈在进程陷入内核态使用,里面保存的是用户栈的地址,以及在进行进程上下文切换时需要保存的参数,返回值等。因为每个进程都有可能发生系统调用陷入内核,从用户态到内核态发生一个中断,那么内核栈就会保存这个进程操作内核的调用信息,如果常驻内存的话就会被下个进程发生的系统调用的信息覆盖掉,所以内核栈是不会常驻内存的
proc存放的是系统经常要查询和修改的信息,需要快速访问,因此常将其装入内存 。
PCB一般包括:
- 程序ID(PID、进程句柄):它是唯一的,一个进程都必须对应一个PID。PID一般是整型数字
- 特征信息:一般分系统进程、用户进程、或者内核进程等
- 进程状态:运行、就绪、阻塞,表示进程现的运行情况
- 优先级:表示获得CPU控制权的优先级大小
- 通信信息:进程之间的通信关系的反映,由于操作系统会提供通信信道
- 现场保护区:保护阻塞的进程用
- 资源需求、分配控制信息
- 进程实体信息,指明程序路径和名称,进程数据在物理内存还是在交换分区(分页)中
- 其他信息:工作单位,工作区,文件信息等
Linux下多线程编程常用的pthread库提供的函数名和意义
pthread_create 创建一个线程
pthread_join用来等待一个线程的结束
pthread_mutex_init 初始化一个线程互斥锁
pthread_exit结束一个线程
进程间通信(IPC,InterProcess Communication)
通信方法:
- 高级通信: 文件记录及锁定,管道、信号、共享内存、Socket通信、远程过程调用(RPC),消息传递(直接:消息缓冲区;间接:信箱)等。
- 低级通信:管程,信号量。
- 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
- 有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
- 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 信号 ( signal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
- 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
- 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
fork
fork()
使用fork函数得到的子进程从父进程的继承了整个进程的地址空间,包括:进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等。
子进程与父进程的区别在于:
1、父进程设置的锁,子进程不继承(因为如果是排它锁,被继承的话,矛盾了)
2、各自的进程ID和父进程ID不同
3、子进程的未决告警被清除;
4、子进程的未决信号集设置为空集。 -mickole博客
- sleep()方法强制使当前线程休眠,释放CPU资源,以便使得其他所有线程有机会运行。
- yield()方法使得当前的线程让出CPU的使用权,以使得比该线程优先级相同或更高的线程有机会运行。该线程在让出CPU使用权之后可能再次被选中,因此yield()方法可能会不起作用(这也说明了yield()方法不会使得比当前线程优先级低的线程运行)。
- java虚拟机中如果多个线程优先级相同,则会随机选择一个线程占用CPU,处于运行状态的线程会一直运行,直至它不得不放弃CPU为止,因此不一定是分时调度。
- 分时调度模型是让所有线程轮流获得CPU使用权。
进程互斥实现机制
信号量
对于记录型信号量,当 s<0 的时候,请求进程会阻塞
对于整型信号量,当s<=0的时候,请求进程不会阻塞,而是进入盲等状态
- -和+,DOWN()和UP(),SLEEP()和WAKEUP(),P()和V(),Wait() 和Signal()。虽然它们名字都不一样,可意思都是相同的。前者是申请资源,后者是释放资源。
生产者与消费者
生产者与消费者
举例:
单生产者单消费者:
#define N 100
typedef int semaphore;
semaphore empty = N;
semaphore full = 0;
void producer(void)
{
int item;
while(TRUE){
item = produce_item();
down(&empty);
insert_item(item);
up(&full);
}
}
void consumer(void)
{
int item;
while(TRUE){
down(&full);
item = remove_item();
up(&empty);
consume_item(item);
}
}
多生产者多消费者:
#define N 100
typedef int semaphore;
semaphore mutex = 1;
semaphore empty = N;
semaphore full = 0;
void producer(void)
{
int item;
while(TRUE){
item = produce_item();
down(&empty);
down(&mutex);
insert_item(item);
up(&full);
up(&mutex);
}
}
void consumer(void)
{
int item;
while(TRUE){
down(&full);
down(&mutex);
item = remove_item();
up(&empty);
up(&mutex);
consume_item(item);
}
}
低级调度
- 如果系统只有用户态线程,则线程对操作系统是不可见的,操作系统只能调度进程;
- 如果系统中有内核态线程,则操作系统可以按线程进行调度;
- 系统态,即内核态,三种情况下会出现由用户态转为内核态,中断,异常,系统调用
网友评论