操作系统编程实践总结
一 线程的创建与启动
1.1 进程与线程
1. 进程
进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。
2. 线程
线程是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。
3.进程和线程的关系
(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。
(3)处理机分给线程,即真正在处理机上运行的是线程。
(4)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
4.进程和线程的区别
(1)进程是资源分配的最小单位,线程是程序执行的最小单位。
(2)进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
(3)线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
(4)多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
1.2 Java中的Thread和Runnable类
在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口。如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
线程池只能放入实现Runnable 类线程,不能直接放入继承Thread的类
1.3 三种创建线程的办法
1.继承Thread类
(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
(2)创建Thread子类的实例,即创建了线程对象。
(3)调用线程对象的start()方法来启动该线程。
2.实现Runnable接口
(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
(2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
(3) 调用线程对象的start()方法来启动该线程。
3.创建FutureTask对象
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
二 线程简单同步(同步块)
2.1 同步的概念和必要性
线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,目前实现线程同步的方法有很多,临界区对象就是其中一种。
在一般情况下,创建一个线程是不能提高程序的执行效率的,所以要创建多个线程。但是多个线程同时运行的时候可能调用线程函数,在多个线程同时对同一个内存地址进行写入,由于CPU时间调度上的问题,写入数据会被多次的覆盖,所以就要使线程同步。
2.2 synchronize关键字和同步块
Java 同步块(synchronized block)用来标记方法或者代码块是同步的。Java 同步块用来避免竞争。
Java 同步关键字(synchronized)
Java 中的同步块用 synchronized 标记。同步块在 Java 中是同步在某个对象上。所有同步在一个对象上的同步块在同时只能被一个线程进入并执行操作。所有其他等待进入该同步块的线程将被阻塞,直到执行该同步块中的线程退出。
有四种不同的同步块:实例方法,静态方法,实例方法中的同步块,静态方法中的同步块。
上述同步块都同步在不同对象上。实际需要那种同步块视具体情况而定。
2.3 实例
(1)实例方法同步
public synchronized void add(int value){
this.count += value;
}
(2)静态方法同步
public static synchronized void add(int value){
count += value;
}
静态方法的同步是指同步在该方法所在的类对象上。因为在 Java 虚拟机中一个类只能对应一个类对象,所以同时只允许一个线程执行同一个类中的静态同步方法。
对于不同类中的静态同步方法,一个线程可以执行每个类中的静态同步方法而无需等待。不管类中的那个静态同步方法被调用,一个类只能由一个线程同时执行。
(3)实例方法中的同步块
public void add(int value){
synchronized(this){
this.count += value;
}
}
示例使用 Java 同步块构造器来标记一块代码是同步的。该代码在执行时和同步方法一样。注意 Java 同步块构造器用括号将对象括起来。在上例中,使用了“this”,即为调用 add 方法的实例本身。在同步构造器中用括号括起来的对象叫做监视器对象。上述代码使用监视器对象同步,同步实例方法使用调用方法本身的实例作为监视器对象。一次只有一个线程能够在同步于同一个监视器对象的 Java 方法内执行。
下面两个例子都同步他们所调用的实例对象上,因此他们在同步的执行效果上是等效的。
public class MyClass {
public synchronized void log1(String msg1, String msg2){
log.writeln(msg1);
log.writeln(msg2);
}
public void log2(String msg1, String msg2){
synchronized(this){
log.writeln(msg1);
log.writeln(msg2);
}
}
}
(4)静态方法中的同步块
public class MyClass {
public static synchronized void log1(String msg1, String msg2){
log.writeln(msg1);
log.writeln(msg2);
}
public static void log2(String msg1, String msg2){
synchronized(MyClass.class){
log.writeln(msg1);
log.writeln(msg2);
}
}
}
(5)Java 同步实例
public class Counter{
long count = 0;
public synchronized void add(long value){
this.count += value;
}
}
public class CounterThread extends Thread{
protected Counter counter = null;
public CounterThread(Counter counter){
this.counter = counter;
}
public void run() {
for(int i=0; i<10; i++){
counter.add(i);
}
}
}
public class Example {
public static void main(String[] args){
Counter counter = new Counter();
Thread threadA = new CounterThread(counter);
Thread threadB = new CounterThread(counter);
threadA.start();
threadB.start();
}
}
三 生产者消费者问题
3.1 问题表述
生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个具有多个缓冲区的缓冲池,生产者将它生产的产品放入一个缓冲区中,消费者可以从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个已经放入产品的缓冲区中再次投放产品。
3.2 实现思路
要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题,常用的方法有信号灯法等。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形。
1.条件变量解决方案:
基于队列构建一个缓冲区,生产者在队尾填充,消费者在队头获取。队列缓冲区作为多个线程的共享资源。
由于多个消费者和生产者线程可以并发访问缓冲区,需要互斥锁来控制对缓冲区的互斥访问。
队列空时消费者线程需要等到队列中存在资源、队列满时生产者线程需要等到队列中有资源被消费。通过使用条件变量来实现线程的阻塞、通知以达到生产、消费线程的同步。
2.信号量解决方案:
通过信号量来控制线程的同步,信号量管理可以获得资源的个数,初始队列为空,写信号量资源个数为队列长度,读信号量资源个数为0。
3.3 Java实现该问题的代码
1.条件变量解决方案:
from threading import Lock
from threading import Condition
import threading
class myQueue:
def __init__(self, size):
self.size = size
self.list = list()
self.lock = Lock()
self.notFullCond = Condition(self.lock)
self.notEmptyCond = Condition(self.lock)
def isFull(self):
if self.size == len(self.list):
return True
return False
def isEmpty(self):
if 0 == len(self.list):
return True
return False
def enQueue(self, elem):
self.lock.acquire()
while self.isFull(): #队列满时触发等待notFullCond条件,线程阻塞同时释放互斥锁
print('queue is full, waiting...')
self.notFullCond.wait()
print(threading.current_thread().getName() + ' product ' + str(elem))
self.list.append(elem)#当有资源进入队列通知所有等待notEmptyCond条件的线程,等释放互斥锁后,等待notEmptyCond条件的线程获取锁,再次判断条件
self.notEmptyCond.notify_all()
self.lock.release()
def deQueue(self):
self.lock.acquire()
while self.isEmpty():#队列空时触发等待notEmptyCond条件,线程阻塞同时释放互斥锁
print('queue is empty, waiting...')
self.notEmptyCond.wait()
elem = self.list[0]
del(self.list[0])
print(threading.current_thread().getName() + ' consume ' + str(elem))#当有资源出队列通知所有等待notFullCond条件的线程,等释放互斥锁后,等待notFullCond条件的线程获取锁,再次判断条件
self.notFullCond.notify_all()
self.lock.release()
return elem
2.条件变量解决方案:
from threading import Lock
from threading import Semaphore
import threading
class mySemQueue:
def __init__(self, size):
self.size = size
self.list = list()
self.lock = Lock()
self.writeSem = Semaphore(size)#初始化写信号量
self.readSem = Semaphore(0) #初始化读信号量
def enQueue(self, elem):
self.writeSem.acquire() #资源入队申请写信号量,如果为0则阻塞
self.lock.acquire()#互斥锁来保证资源的互斥访问
self.list.append(elem)
print(threading.current_thread().getName() + ' product ' + str(elem))
self.lock.release()
self.readSem.release()#资源入队后释放一个读信号量,如果其它线程阻塞在这个信号量上,唤醒该线程
def deQueue(self):
self.readSem.acquire() #资源出队申请读信号量,如果为0则阻塞
self.lock.acquire()
elem = self.list[0]
del(self.list[0])
print(threading.current_thread().getName() + ' consume ' + str(elem))
self.lock.release()
self.writeSem.release()#资源出队后释放一个写信号量,如果其它线程阻塞在这个信号量上,唤醒该线程
return elem
3.4 测试
from threading import Thread
import sys
import threading
class myThread(Thread):
def __init__(self, func):
Thread.__init__(self)
self.func = func
def run(self):
print(threading.current_thread().getName() + ' start')
self.func()
from myThread import myThread
from myQueue import myQueue
import random
import sys
def producter():
while True:
elem =random.randint(1, 100)
que.enQueue(elem)
def consumer():
while True:
que.deQueue()
fp = open('log.txt','w')
sys.stdout = fp
que = myQueue(10)
t1 = myThread(producter)
t2 = myThread(consumer)
t3 = myThread(consumer)
t1.start()
t2.start()
t3.start()
3.4.1 当生产能力超出消费能力时的表现
若生产者进程已经将缓冲区放满,消费者进程并没有取产品,即 empty = 0,当下次仍然是生产者进程运行时,它先执行 P(mutex)封锁信号量,再执行 P(empty)时将被阻塞,希望消费者取出产品后将其唤醒。轮到消费者进程运行时,它先执行 P(mutex),然而由于生产者进程已经封锁 mutex 信号量,消费者进程也会被阻塞,这样一来生产者进程与消费者进程都将阻塞,都指望对方唤醒自己,陷入了无休止的等待
3.4.2 当生产能力弱于消费能力时的表现
若消费者进程已经将缓冲区取空,即 full = 0,下次如果还是消费者先运行,也会出现类似的死锁。
四 总结
进程/线程同步问题是操作系统在数据共享方面的一大问题,其不仅需要硬件及系统级的实现,同时还需要程序员在开发时避免死锁,同步问题与死锁问题密不可分。
代码 一
package goufan;
/**
* Runable的实现类,是线程执行的主体。
* run函数是入口。
* @author Administrator
*
*/
class MyR implements Runnable{
private String msg;
public MyR(String msg) {
// TODO Auto-generated constructor stub
this.msg = msg;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
try {
Thread.sleep(1000);
System.out.println(msg);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
break;
}
}
}
}
public class TextThread {
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread thread1 = new Thread(new MyR("hello"));
thread1.start();
Thread thread2 = new Thread(new MyR("wowo"));
thread2.start();
}
}
代码 二
package goufan;
import goufan.M;
public class M {
public static void main(String[] args) {
// TODO Auto-generated method stub
M m = new M();
Runnable runnable = new Runnable() {
@Override
public void run() {
while(true) {
try {
System.out.println("haha");
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
break;
}
}
}
};
Thread thread = new Thread(runnable );
thread.start();
}
}
代码 三
package goufan;
public class goufan3 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("haha");
}
}).start();
new Thread(()->{
System.out.println("haha");
}).start();
}
}
代码 四
package goufan;
import java.util.ArrayList;
public class test {
static int c = 0;
static Object lock = new Object();
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread[] threads = new Thread[1000];
for(int i = 0;i<1000;i++) {
final int index = i;
threads[i] = new Thread(()->{
synchronized (lock) {
System.out.println("thread "+index+"enter");
int a = c;
a++;
try {
Thread.sleep((long) (Math.random()*10));
} catch (InterruptedException e) {
e.printStackTrace();
}
c=a;
System.out.println("thread "+index+"leave");
}
});
threads[i].start();
}
for(int i=0;i<1000;i++) {
try {
threads[i].join();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("c="+c);
}
}
代码 五
package goufan;
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Queue { //队列
private Lock lock =new ReentrantLock();
private Condition fullC;
private Condition emptyC;
private int size;
public Queue(int size) {
this.size = size;
fullC = lock.newCondition();
emptyC = lock.newCondition();
}
LinkedList<Integer> list = new LinkedList<Integer>();
/**
* 入队
* @return
*/
public boolean EnQueue(int data) {
lock.lock();
while(list.size()>=size) {
try {
fullC.await();
}catch(InterruptedException e) {
lock.unlock();
return false;
}
}
list.addLast(data);
emptyC.signalAll();
lock.unlock();
return true;
}
/**
* 出队
* @return
*/
public int DeQueue() {
lock.lock();
while(list.size() == 0) {
try {
emptyC.await();
}catch(InterruptedException e) {
lock.unlock();
return -1;
}
}
int r = list.removeFirst();
fullC.signalAll();
lock.unlock();
return r;
}
public boolean isFull() {
return list.size()>=size;
}
public boolean isEmpty() {
return list.size()==0;
}
}
代码 六
package goufan;
public class TestPC {
static Queue queue = new Queue(5);
public static void main(String[] args) {
//创建三个生产者
for(int i=0;i<3;i++) {
final int index = i;
new Thread(()-> {
while(true) {
int data =(int)(Math.random()*1000);
System.out.printf("thread %d want to EnQueue %d\n",index,data);
queue.EnQueue(data);
System.out.printf("thread %d EnQueue %d Success\n",index,data);
sleep();
}
}).start();
}
for(int i=0;i<3;i++) {
final int index = i;
new Thread(()->{
while(true) {
System.out.printf("customer thread %d want to EnQueue\n",index);
int data = queue.DeQueue();
System.out.printf("customer thread %d EnQueue %d Success\n",index,data);
sleep();
}
}).start();
}
}
public static void sleep() {
int t = (int)(Math.random()*100);
try {
Thread.sleep(t);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
操作系统编程实践总结相关推荐
- 《深入理解大数据:大数据处理与编程实践》一一1.2 大数据处理技术简介
本节书摘来自华章计算机<深入理解大数据:大数据处理与编程实践>一书中的第1章,第1.2节,作者 主 编:黄宜华(南京大学)副主编:苗凯翔(英特尔公司),更多章节内容可以访问云栖社区&quo ...
- TinyDDS编程实践
背景 传统计算机网络的运行依赖于集中式运营商及服务提供商(Server),它在拓扑结构上的典型特点就是存在一个或数个"中心节点".整个网络的数据传输和处理都集中在少数节点上.&qu ...
- 《Unix-Linux编程实践教程》读书笔记(一)
写在最前: 第一遍:零零散散的花了近两个月的时间读了本书的第一遍,这是一本很适合刚刚掌握一些计算机基础知识的人读的书.学习的思路非常明确,能做什么?怎么做?自己动手?三个方面符合认知顺序,由浅入深让人 ...
- 【编程实践】Linux / UNIX Shell编程极简教程
不同于一般的介绍Linux Shell 的文章,本文并未花大篇幅去介绍 Shell 语法,而是以面向"对象" 的方式引入大量的实例介绍 Shell 日常操作,"对象&qu ...
- 【3月15日】BIO、伪异步IO以及NIO编程实践
1.引言 从java的I/O体系发展的历史看,先有java.io,后有java.nio.前者一般称之为IO,后者称之为NIO(New IO).但是又由于其特性前者又成为BIO(Block IO),后者 ...
- 机器人编程实践-ROS2基础与应用-
这是机器人编程实践的第4版课程说明,分别在2016年开设第一版,2017年第二版,2018年第三版,2019年第四版,每版课程内容经过2轮测试,非常感谢对课程提出宝贵意见的同学们以及热心的博客朋友. ...
- 《9.linux网络编程实践》
转自 https://edu.csdn.net/lecturer/505 朱老师物联网大讲堂 <9.linux网络编程实践> 第一部分.章节目录 3.9.1.linux网络编程框架 3.9 ...
- Elmo运动控制器 —— Maestro Software编程实践指南
文章目录 1 Projects and files 1.1 Project Location and Naming 1.2 Project's Files 1.3 Project's Descript ...
- 树莓派Pico迷你开发板MicroPython多线程编程实践
内容目录: 一.多线程基本知识 二.MicroPython/Python低层多线程API介绍 三.树莓派Pico 开发板MicroPython多线程编程实践举例 3.1 Pico RP2040 MCU ...
最新文章
- php批量生成产品编号:xxx.000001,并依次递增
- word2010多级列表编号变成黑块的解决方案
- python的模块、包、库区别。导入包/模块的方式
- 性能测试之JMeter接口关联【JSON提取器】详解
- vue Class 与 Style 绑定
- 三款JSON类库Jackson,Gson与JSON-lib的性能对比
- 实验3-5 查询水果价格 (15 分)
- java 修改mysql密码_mysql数据库忘记密码时如何修改
- Android 系统架构图
- UTM投影的选择(地区-投影带)
- mysql 防止网络爬虫_Nginx反爬虫策略,防止UA抓取网站
- java多线程简单模拟12306抢票
- 数学建模学习17(最短距离、BP神经网络)
- UART串行通信模式
- 大数据时代_如何构建国家地质基础数据更新体系
- 【微信小程序入门到精通】—小程序实战构建售货平台首页
- MINTEL-重启零售:线上加实体——附下载链接
- 计算机常用英语词汇及读音,表示显示的英文单词与标准读音
- alfresco源代码的编译
- c语言文本相似度分析系统,某课程设计---文件相似度判断
热门文章
- 持续集成(三)- hudson插件入门
- 差分隐私 深度学习_深度学习中的差异隐私
- 三层架构编程、DAO层、Entity层、Service层、Controller层
- 【面经——广州道一云+笔试+一二三面+HR面+offer】
- [WinCE] Win CE 屏幕截图
- autojs-获取api接口JSON值
- 搜狗搜索App停止服务
- java简单投票系统_基于SpringBoot的简约投票系统
- 人生就是一次充满未知的旅行,在乎的是沿途的风景,在乎的是看风景的心情, 旅行不会因为美丽的风景终止。
- UVA - 12627 Erratic Expansion(分治)