PostgreSQL jsonb 使用入门

json 类型 说明 根据RFC 7159中的说明,JSON 数据类型是用来存储 JSON(JavaScript Object Notation)数据的。这种数据也可以被存储为text,但是 JSON 数据类型的优势在于能强制要求每个被存储的值符合 JSON 规则。也有很多 JSON 相关的函数和操作符可以用于存储在这些数据类型中的数据 PostgreSQL支持两种 JSON 数据类型:json 和 jsonb。它们几乎接受完全相同的值集合作为输入。两者最大的区别是效率。json数据类型存储输入文本的精准拷贝,处理函数必须在每 次执行时必须重新解析该数据。而jsonb数据被存储在一种分解好的二进制格式中,因为需要做附加的转换,它在输入时要稍慢一些。但是 jsonb在处理时要快很多,因为不需要重新解析。 重点:jsonb支持索引 由于json类型存储的是输入文本的准确拷贝,存储时会空格和JSON 对象内部的键的顺序。如果一个值中的 JSON 对象包含同一个键超过一次,所有的键/值对都会被保留(** 处理函数会把最后的值当作有效值**)。 jsonb不保留空格、不保留对象键的顺序并且不保留重复的对象键。如果在输入中指定了重复的键,只有最后一个值会被保留。 推荐把JSON 数据存储为jsonb 在把文本 JSON 输入转换成jsonb时,JSON的基本类型(RFC 7159 )会被映射到原生的 PostgreSQL类型。因此,jsonb数据有一些次要额外约束。 比如:jsonb将拒绝除 PostgreSQL numeric数据类型范围之外的数字,而json则不会。 JSON 基本类型和相应的PostgreSQL类型 JSON 基本类型 PostgreSQL类型 注释 string text 不允许\u0000,如果数据库编码不是 UTF8,非 ASCII Unicode 转义也是这样 number numeric 不允许NaN 和 infinity值 boolean boolean 只接受小写true和false拼写 null (无) SQL NULL是一个不同的概念 json 输入输出语法 -- 简单标量/基本值 -- 基本值可以是数字、带引号的字符串、true、false或者null SELECT '5'::json; -- 有零个或者更多元素的数组(元素不需要为同一类型) SELECT '[1, 2, "foo", null]'::json; -- 包含键值对的对象 -- 注意对象键必须总是带引号的字符串 SELECT '{"bar": "baz", "balance": 7.77, "active": false}'::json; -- 数组和对象可以被任意嵌套 SELECT '{"foo": [true, "bar"], "tags": {"a": 1, "b": null}}'::json; -- "->" 通过键获得 JSON 对象域 结果为json对象 select '{"nickname": "goodspeed", "avatar": "avatar_url", "tags": ["python", "golang", "db"]}'::json->'nickname' as nickname; nickname ------------- "goodspeed" -- "->>" 通过键获得 JSON 对象域 结果为text select '{"nickname": "goodspeed", "avatar": "avatar_url", "tags": ["python", "golang", "db"]}'::json->>'nickname' as nickname; nickname ----------- goodspeed -- "->" 通过键获得 JSON 对象域 结果为json对象 select '{"nickname": "goodspeed", "avatar": "avatar_url", "tags": ["python", "golang", "db"]}'::jsonb->'nickname' as nickname; nickname ------------- "goodspeed" -- "->>" 通过键获得 JSON 对象域 结果为text select '{"nickname": "goodspeed", "avatar": "avatar_url", "tags": ["python", "golang", "db"]}'::jsonb->>'nickname' as nickname; nickname ----------- goodspeed 当一个 JSON 值被输入并且接着不做任何附加处理就输出时, json会输出和输入完全相同的文本,而jsonb 则不会保留语义上没有意义的细节 ...

2019-05-30 · 10 min · 1984 words

并发模型:线程与锁

并发&并行 并发程序含有多个逻辑上的独立执行块,他们可以独立的并行执行,也可以串行执行。 并行程序解决问题的速度比串行程序快的多,因为其可以同时执行整个任务的多个部分。并行程序可能有多个独立执行块,也可能只有一个。 引用Rob Pike的经典描述就是: 并发是同一时间应对多件事情的能力; 并行是同一时间动手做多件事情的能力。 常见的并发模型有: 线程与锁 函数式编程 actor模型和通信顺序是进行(Communicating Sequential Processes, CSP) 数据级并行 lambda 架构 分离标识与状态模型 这篇主要介绍线程与锁模型 线程与锁模型 线程与锁模型是对底层硬件运行过程的形式化,非常简单直接,几乎所有的编程语言都对其提供了支持,且不对其使用方法加以限制(易出错)。 这篇文章主要使用python语言来演示线程与锁模型。文章结构来自《七周七并发模型》 互斥和内存模型 创建线程 from threading import Thread def hello_world(): print("Hello from new thread") def main(): my_thread = Thread(target=hello_world) my_thread.start() print("Hello from main thread") my_thread.join() main() 这段代码创建并启动了一个Thread实例,首先从start() 开始,my_thread.start() main()函数的余下部分一起并发执行。最后调用join() 来等待my_thread线程结束。 运行这段代码输出结果有几种: Hello from new thread Hello from main thread 或者 Hello from main thread Hello from new thread 或者 ...

2019-05-19 · 6 min · 1178 words

垃圾回收算法|引用计数法

本文是《垃圾回收的算法与实现》读书笔记 上一篇为《GC 标记-清除算法》 引用计数算法 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器的值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。这也就是需要回收的对象。 引用计数算法是对象记录自己被多少程序引用,引用计数为零的对象将被清除。 计数器表示的是有多少程序引用了这个对象(被引用数)。计数器是无符号整数。 计数器的增减 引用计数法没有明确启动 GC 的语句,它与程序的执行密切相关,在程序的处理过程中通过增减计数器的值来进行内存管理。 new_obj() 函数 与GC标记-清除算法相同,程序在生成新对象的时候会调用 new_obj()函数。 func new_obj(size){ obj = pickup_chunk(size, $free_list) if(obj == NULL) allocation_fail() else obj.ref_cnt = 1 // 新对象第一只被分配是引用数为1 return obj } 这里 pickup_chunk()函数的用法与GC标记-清除算法中的用法大致相同。不同的是这里返回 NULL 时,分配就失败了。这里 ref_cnt 域代表的是 obj 的计数器。 在引用计数算法中,除了连接到空闲链表的对象,其他对象都是活跃对象。所以如果 pickup_chunk()返回 NULL,堆中也就没有其它大小合适的块了。 update_ptr() 函数 update_ptr() 函数用于更新指针 ptr,使其指向对象 obj,同时进行计数器值的增减。 func update_ptr(ptr, obj){ inc_ref_cnt(obj) // obj 引用计数+1 dec_ref_cnt(*ptr) // ptr之前指向的对象(*ptr)的引用计数-1 *ptr = obj } 这里 update_ptr 为什么需要先调用 inc_ref_cnt,再调用dec_ref_cnt呢? 是因为有可能 *ptr和 obj 可能是同一个对象,如果先调用dec_ref_cnt可能会误伤。 **inc_ref_cnt()**函数 这里inc_ref_cnt函数只对对象 obj 引用计数+1 func inc_ref_cnt(obj){ obj.ref_cnt++ } dec_ref_cnt() 函数 ...

2018-08-12 · 3 min · 429 words

垃圾回收算法|GC标记-清除算法

本文是《垃圾回收的算法与实现》读书笔记 什么是GC标记-清除算法(Mark Sweep GC) GC 标记-清除算法由标记阶段和清除阶段构成。在标记阶段会把所有的活动对象都做上标记,然后在清除阶段会把没有标记的对象,也就是非活动对象回收。 名词解释: 在 GC 的世界里对象指的是通过应用程序利用的数据的集合。是 GC 的基本单位。一般由头(header)和域(field)构成。 活动对象:能通过引用程序引用的对象就被称为活动对象。(可以直接或间接从全局变量空间中引出的对象) 非活动对象:不能通过程序引用的对象呗称为非活动对象。(这就是被清除的目标) 标记-清除算法的伪代码如下所示: func mark_sweep(){ mark_phase() // 标记阶段 sweep_phase() // 清除阶段 } 标记阶段 标记阶段就是遍历对象并标记的处理过程。 标记阶段伪代码如下: func mark_phase(){ for (r : $roots) // 在标记阶段,会给所有的活动对象打上标记 mark(*r) } func mark(){ if (obj.mark == False) obj.mark = True // 先标记找出的活动对象 for (child: children(obj)) // 然后递归的标记通过指针数组能访问到的对象 mark(*child) } 这里 $root 是指针对象的起点,通过$root 可以遍历全部活动对象。 下图是标记前和标记后内存中堆的状态 清除阶段 在清除阶段,collector 会遍历整个堆,回收没有打上标记的对象(垃圾),使其能再次利用。 sweep_phase() 函数伪代码实现如下: func sweep_phase(){ sweeping = $heap_start // 首先将堆的首地址赋值给 sweeping while(sweeping < $head_end){ if(sweeping.mark == TRUE) // 如果是标记状态就设为 FALSE,如果是活动对象,还会在标记阶段被标记为 TRUE sweeping.mark == FALSE else: sweeping.next = $free_list // 将非活动对象 拼接到 $free_list 头部位置 $free_list = sweeping sweeping += sweeping.size } } size 域指的是存储对象大小的域,在对象头中事先定义。 ...

2018-07-21 · 2 min · 348 words

Golang 学习笔记-2:控制流

上一篇我们了解了golang 的变量、函数和基本类型,这一篇将介绍一下控制流 现在我们看一个复杂点的例子: fibonacci(递归版) package main import "fmt" func main() { result := 0 for i := 0; i <= 10; i++ { result = fibonacci(i) fmt.Printf("fibonacci(%d) is: %d\n", i, result) } } func fibonacci(n int) (res int) { if n <= 1 { res = 1 } else { res = fibonacci(n-1) + fibonacci(n-2) } return } // outputs fibonacci(0) is: 1 fibonacci(1) is: 1 fibonacci(2) is: 2 fibonacci(3) is: 3 fibonacci(4) is: 5 fibonacci(5) is: 8 fibonacci(6) is: 13 fibonacci(7) is: 21 fibonacci(8) is: 34 fibonacci(9) is: 55 fibonacci(10) is: 89 for i := 0; i <= 10; i++ {} 第7行是一个循环结构 这里for 循环是一个控制流 控制流 For Go 只有一种循环接口– for 循环 ...

2018-04-17 · 5 min · 896 words

《理解 unix 进程》笔记-1

UNIX 进程 系统调用 Unix 系统是由用户空间(userland)和内核组成。Unix 内核位于计算机硬件之上,是与硬件交互的中介。这些交互包括通过问卷系统进程读/写、在网络上发送数据、分配内存,以及通过扬声器播放音频。这些都是用户应用程序所不能涉及的,只能通过系统调用来完成。 系统调用为内核和用户空间搭建了桥梁。规定了程序和计算机硬件直接所允许发生的一切交互。 进程是 Unix 系统的基石,所有的代码都是在进程中运行。 unix 中的进程创建是通过内核系统调用 fork() 实现的。当一个进程产生一个 fork 请求时,操作系统执行以下功能: 为新进程在进程表中分配一个空项 为子进程赋一个唯一的进程标识符 为一个父进程上下文的逻辑副本,不包括共享内存区 增加父进程拥有的所有文件的计数器,以表示有一个另外的进程现在也用户这些文件。 把子进程置为就绪态 向父进程返回子进程的进程号;对子进程返回0。 所有这些操作都在父进程的内核态下完成。 进程皆有标识 在系统中运行的所有进程都有一个唯一的进程标识符,称为 pid。 pid 并不传达关于进程本身的任何信息,仅仅是一个数字标识 在 python 中查看当前进程 pid 可以使用 getpid() 方法。 >>> import os >>> print os.getpid() 26164 在实际应用中,pid 可以加入都日志信息中,这样当多个进程向同一个文件写入日志的时候,就可以知道哪一行是由哪个进程写入的。 进程皆有父 系统中运行的每一个进程都有对应的父进程。每个进程都知道它父进程的标识符(ppid)。 在 python 中查看当前进程 pid 可以使用 getppid() 方法。 >>> import os >>> print os.getpid() 26164 >>> print os.getppid() 26125 进程皆有文件描述符 在 Unix 中,一切都是文件。 ...

2018-03-25 · 3 min · 518 words

操作系统线程描述

这是操作系统进程系列文章第三篇-操作系统线程描述 文章是《操作系统-精髓与设计原理》学习笔记 线程(thread) 什么是线程 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 关于进程的两个概念: 资源所有权:一个进程包括一个存放进程映像的虚拟地址空间(进程映像是程序、数据、栈和进程控制块中定义的属性的集合)。一个进程总是拥有对资源的控制或所有权,这些资源包括内存、I/O 通道,I/O 设备和文件。 调度/执行:一个进程沿着通过一个或多个程序的一条执行路径执行,其执行过程可能与其他进程的执行过程交替执行。一个进程具有一个执行状态和一个分片的优先级,并且是一个可被操作系统调度和分配的实体。 这两个概念是独立的,操作系统可以独立的处理。 现代操作系统通常把分派单位称为线程(或轻量级进程),拥有资源所有权的单位称为进程。 多线程 多线程是指操作系统在单个进程内支持多个并发执行路径的能力。每个进程中只有一个线程在执行的方法称为单线程方法。进程支持多个线程的情况被称作多线程。 在多线程环境中,进程被定义成资源分配的单位和一个被保护的单位,与进程相关联的有: 存放进程映像的虚拟地址空间 受保护的对处理器、其他进程、文件和 I/O 资源的访问 在一个进程中,可能有一个或多个线程,每个线程有: 线程的执行状态(运行,就绪) 在未运行时保存的线程上下文 一个执行栈 用于每个线程局部变量的静态存储空间 与进程内的其他线程共享的对进程的内存和资源的访问 进程 VS 线程 下图说明了进程和线程的区别: 在单线程模型中,进程的标出包括他的进程控制块和用户地址空间,以及在进程执行中管理调用/返回 行为的用户栈和内核栈。当进程被控制时,处理器寄存器被该进程锁控制;当进程不运行时,这些处理器寄存器的内容被保存。 在多线程环境中,进程仍然只有一个与之关联的进程控制块和用户地址空间。但是每个线程都有一个独立的栈,还有独立的控制块用于包含寄存器值、优先级和其他与线程相关的状态信息。 进程中的所有线程共享该进程的状态和资源,它们驻留在同一块地址空间中,并且可以访问到相同的数据。当一个线程改变了内存中的一个数据项时,其他线程在访问这一数据项时能够看到变化后的结果。 线程的优点 在一个已有的进程中创建一个新的线程比创建一个全新的进程所需时间要少的多。 终止一个线程比终止一个进程花费的时间少 同一个进程内线程间切换比进程间切换花费的时间要少。 线程提高了不同的执行程序间通信的效率。(大多数操作系统中,独立进程间的通信需要内核的介入,由于同一进程中的线程共享内存和文件,它们间的通信无需调用内核) 线程状态 和进程一样,线程的关键状态有运行态、就绪态和阻塞态。挂起是进程级别的概念,一个进程被换出,它的所有线程都被换出。 有4个与线程状态改变相关的操作: 派生:当派生一个新进程时,同时也为改进程派生出一个线程。进程中的线程也可以在同一个进程中派生另一个线程,新的线程拥有自己的寄存器上下文和栈空间,且被放置在就绪队列中。 阻塞:当线程需要等待一个事件时,将被阻塞,此时处理器转而执行另一个就绪线程(可能是同一进程,也可能是不同进程) 解除阻塞:当阻塞一个线程的事件发生时,该线程被转移到就绪队列中 结束:当一个线程完成时,其寄存器上下文和栈都被释放。 用户级线程和内核级线程 线程的实现可以分为两大类:用户级线程(User-Level Thread ULT)和内核级线程(Kernel-Level Thread KLT)。 在用户级线程和内核级线程使用时,通常有以下三种模式: 在一个纯粹的用户级线程程序中,有关线程管理的所有工作都由应用程序完成,内核意识不到线程的存在。 使用用户级线程的优点: 线程切换不需要内核态特权,因此,进程不需要为了线程管理而切换到内核态,这节省了两次状态转换(从用户态到内核态,再从内核态返回用户态)的开销。 调度可以是用户程序相关的。(可以为特定的应用使用特定的调度算法) 用户级线程可以在任何操作系统中运行,不需要对底层内核进行修改以支持用户级线程。 使用用户级线程的缺点: 许多系统调用会被阻塞。因此当用户级线程执行一个系统调用时,不仅这个线程会被阻塞,进程中所有线程都会被阻塞。 不能使用多个处理器。内核一次只把一个进程分配给一个处理器,因此一个进程中只有一个线程可以执行。 解决这两个问题有两种方式: 使用多进程代替多线程,但这样消除了多线程的优势 使用 jacketing 技术。把一个产生阻塞的系统调用转换成一个非阻塞的系统调用。 在一个纯粹的内合辑线程程序中,有关线程管理的所有工作都由内核完成。内核为进程及其内部的每个线程维护上下文信息。调度由内核基于线程完成。 使用内核级线程客服了用户级线程的两个基本缺陷。首先内核可以把同一个进程的多个线程调度到多个处理器;其次一个进程中的线程被阻塞,内核可以调度同一个进程的另一个线程。 ...

2018-03-24 · 1 min · 114 words

操作系统进程描述

这是操作系统进程系列文章第二篇-操作系统进程描述 进程 什么是进程 在给进程下定义前,先考虑以下几个概念: 一个计算机平台包括一组硬件资源:比如处理器、内存、I/O 模块、定时器和磁盘驱动器等。 计算机程序是为执行某些任务而开发的。典型情况下,它们接受外来的输入,做一些处理后,输出结果。 直接根据给定的硬件平台写应用程序效率是低下的 开发操作系统是为了给应用程序提供一个方便、安全和一直的接口。操作系统是计算机硬件和应用程序直接的一层软件,对应用程序和工具提供了支持。 可以把操作系统想象为资源的统一抽象表示,可以被应用程序请求和访问。资源包括内存、网络接口和文件系统等。 有了上述概念,现在就可以讨论操作系统怎样以一个有序的方式管理应用程序的执行,以达到以下目的: 资源对多个应用程序是可用的 物理处理器在多个应用程序间切换以保证所有程序都在执行中 处理器和 I/O 设备能得到充分的利用 现代操作系统采用的方法都是依据对应于一个或多个进程存在的应用程序执行的一种模型。 关于进程有很多定义: 一个正在执行的程序 计算机中正在运行的程序的一个实例 可以分配给处理器并由处理器执行的一个实体 由单一的顺序的执行线程、一个当前状态和一组相关的系统资源所描述的活动单元 进程状态 一个被执行的程序,操作系统会为该程序创建一个进程或任务,并且控制进程的执行。 简单来说,程序只有两种状态:运行态、未运行态。 当操作系统创建一个新进程时,它将该进程以未运行态加入到系统中,操作系统知道进程的存在,并等待执行机会。 当前运行的进程不时中断,操作系统的分派器将选择一个新进程运行。 前一个进程从运行态转换到未运行态,另一个从未运行态转换到运行态。 同时,未运行的进程需保持在某种类型的队列中,并等待它们的执行时机。 上图中的排队图可以描述分派器的行为:被中断的进程转移到等待进程队列中,或者,如果进程以及结束或取消,则被销毁。在任何一种情况下,分派器均从队列中选择一个进程来执行。 通过这个模型,可以看出操作系统需要用某种方式来表示每个进程,使得操作系统能够跟踪它,也就是说需要有一些与进程相关的信息,包括进程在内存中的状态和位置,即进程控制块。 进程控制块 进程在任意时间都可以唯一地被表征为以下元素: 标识符:存储在进程控制块中的数字标识符,包括(次进程的标识符-进程 ID,父进程标识符,用户标识符-用户 ID) 状态:进程状态(如运行态,就绪态,等待态等) 优先级:用于描述进程调度优先级的一个或多个域。 程序计数器:程序中即将被执行的下一条指令的地址 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享内存块的指针 上下文数据:进程执行时处理器的寄存器的数据 I/O 状态信息:包括显示的 I/O 请求、分配给进程的 I/O 设备和被进程使用的文件列表等 记账信息:可能包括处理器时间总和、使用的时钟数总和、时间限制、记账号等。 这些信息被存放在一个叫进程控制块的数据结构中,它由操作系统创建和管理。进程控制块是进程存在的唯一标志,也就是说任何一个进程只要进程创建了它就一定有一个跟它相对应的进程控制块,进程结束了进程控制块就会被操作系统回收,进程在执行的过程对进程的所有操作都是通过进程控制块来实现的。 进程创建和终止 进程除运行和未运行外,在进程的生命周期中,创建和终止都是不可避免的。 进程创建 通常有4个事件会导致创建一个进程: 新的批量作业 交互登录。终端用户登录到系统 操作系统因为提供一项服务而创建。操作系统可以创建一个进程,代表用户程序执行一个功能,使用户无需等待。 由现有进程派生。基于模块化的考虑,或者为了开发并行性,用户程序可以指示创建多个进程。 当一个进程派生另一个进程时,前一个称为父进程,被派生的被称为子进程。 一旦操作系统决定创建一个新进程,它就会按以下步骤进行: 给新进程分配一个唯一的进程标识符。 给进程分配空间。 初始化进程控制块。 设置正确的连接。(例如,如果操作系统把每个调度队列都保存成链表,则新进程必须放置在就绪或就绪/挂起链表中)。 创建或扩充其他数据结构。 进程终止 有很多事件可以导致进程终止,比如: 进程完成 进程超时。进程运行时间超过规定的时限 无可用内存 I/O 失败 算术错误 无效指令 父进程终止 父进程请求 。。。 五状态模型 系统中还存在着一些处于非运行状态但已经就绪等待执行的进程,而且还存在另一些处于阻塞状态等待 I/O 操作结束的进程。 ...

2018-03-20 · 1 min · 173 words

操作系统发展和进程简介

这是操作系统进程系列文章第一篇-操作系统发展和进程简介 操作系统的发展 串行处理 对于早期计算机(20世纪40年代后期到20世纪50年代中期),因为没有操作系统,程序员都是直接与计算机硬件打交道。这些机器都在一个控制台上运行,控制台包括显示灯、触发器、某种类型的输入设备和打印机。用机器代码编写的程序通过输入设备载入计算机。如果程序因错误停止,错误原因由指示灯只是。如果程序运行结束,结果将出现在打印机中。 早期系统主要有两个问题: 调度: 大多数设备使用一个硬拷贝的登记表预定时间。如果用户预定了一个小时,半小时就运行结束,计算机将闲置30分钟,而如果没有在一个小时内运行结束,程序也会被强制停止。 准备时间:一个程序称为一个作业,运行一个程序可能需要往内存中加载编译器和程序语言,保存编译程序,加载目标程序和公用函数变链接在一起。每一步都可能需要安装拆卸硬件,如果这些步骤出现错误,只能重新开始,会占用太多时间。 这种模式称为串行处理,用户必须顺序访问计算机。 简单批处理系统 早期计算机非常贵,调度和准备又非常浪费时间和资源,为了最大限度的利用处理器,当时的研究人员开发了批处理操作系统。 第一个批处理操作系统(也是第一个操作系统)是20世纪50年代中期由 General Motors 开发的,用在 IBM 701上。 简单批处理方案的中心思想是使用一个称作监控程序的软件。通过使用这类操作系统,用户不再直接访问机器,相反,用户把卡片或磁带中的作业提交给计算机管理员,由他把这些作业按顺序组织成一批,并将整个批作业放在输入设备上,供监控程序使用。每个程序完成批处理后返回到监控程序,同事监控程序自动加载下一个程序。 我们可以从两个角度分析这个方案是如何工作的: 监控程序角度: 监控程序为了能一直控制事件的顺序,需要总是处于内存中并且可以执行。监控程序每次从输入设备中读取一个作业,读入后,当前作业被放置在用户程序区域,并把控制权交给这个作业。作业完成后,控制权交还给监控程序,监控程序再读入下一个作业。 处理器角度: 从这个角度看,处理器执行内存中存储的监控程序的指令,这些指令读入下一个作业并存储到内存中的另一个部分。一旦已经读入一个作业,处理器将会遇到监控程序的分支指令,分支指令指导处理器在用户程序开始处继续执行。处理器继而执行用户程序直到执行结束或者遇到错误。无论哪种情况,处理器都将从监控程序读入下一个指令。 控制权交给作业仅仅意味着处理器当前取和执行的都是用户程序中的指令,而控制权交给监控程序的意思是处理器当前从监控程序中取指令并且执行指令。 监控程序或者批处理操作系统,只是一个简单的计算机程序。它依赖于处理器可以从内存的不同部分取指令的能力,以交替的获取或释放控制权。此外,还要考虑其他硬件功能: 内存保护:当用户程序在运行时,不能改变包含监控程序的内存区域 定时器:用户防止一个作业独占系统。作业开始时,设置定时器,时间到,用户程序将被停止 特权指定:某些指令设计成特权指令,只能由监控程序执行。 中断:早期的计算机模型没有中断能力。这个特征使得操作系统在让用户程序放弃控制权或从用户程序获得控制权时具有更大的灵活性。 多道程序设计批处理(多任务处理)系统 虽然简单的批处理系统可以提供自动作业序列,但由于 I/O 设备处理速度相对于处理器速度太慢,处理器仍然经常空闲。这个时候多道程序设计/多任务处理方案就被提了出来。 它的工作原理是:基于内存空间可以保存操作系统和一个用户程序,假设内存空间容得下操作系统和两个用户程序,那么当一个作业需要等待 I/O 时,处理器可以切换到另一个可能并不在等待 I/O 的作业。进一步还可以扩展存储器以保存三个、四个或更多的程序,并在它们之间进行切换。 多道程序操作系统比单个程序或单道程序系统相对要复杂一些。对准备运行的多个作业,它们必须保存在内存中,这就需要内存管理。此外,如果多个作业都准备运行,处理器还必须决定运行哪一个,这需要某种调度算法。 多道程序设计是为了让处理器和 I/O 设备同时保持忙状态,以实现最大效率。其关键机制是:在响应表示 I/O 事务结束的信号时,操作系统对内存中驻留的不同程序进行处理器切换。 分时系统 通过使用多道程序设计,可以使批处理更加有效,但是对许多作业来说,需要提供一个交互模式,以使用户可以和计算机交互。 因为当时的计算机特别昂贵且巨大,普通用户也买不起,分时操作系统应运而生。 和多道程序设计允许处理器同时处理多个批作业一样,它还可以用于处理多个交互作业。 多个用户分享处理器的时间,因而该技术成为分时。 分时系统中,多个用户可以通过终端同时访问系统,由操作系统控制每个用户程序以很短的时间为单位交替执行。 如果有 n 个用户同时请求服务,若不计算操作系统的开销,每个用户平均只能得到1/n 计算机的有效速度,但由于人的反应时间相对计算机比较慢,所以一个设计良好的操作系统,其响应时间可以接近于计算机的时间。 批处理多道程序设计和分时的比较 项目 批处理多道程序设计 分时 主要目标 充分使用处理器 减小响应时间 操作系统指令源 作业提供的作业控制语言命令 从终端键入的命令 第一个分时操作系统是由麻省理工学院开发的兼容分时系统(CTSS)。系统运行在一台内存为32000个36位字的机器上,常驻程序占用了5000个。当控制权被分配给一个交互用户时,改用户的程序和数据被载入到内存剩余的27000个字的空间中。程序通常在第5000个字单元处开始被载入,系统时钟以大约没0.2秒一个的速度产生中断,在每个中断处,操作系统恢复控制权,并将处理器分配给下一个用户。因此,在固定的时间间隔内,当前用户被剥夺,另一个用户被载入。这项技术称为时间片技术。 操作系统是最复杂的软件之一,操作系统开发中有5个重要的理论进展:进程、内存管理、信息保护和安全、调度和资源管理、系统结构。 进程 进程的概念是操作系统结构的基础,这个属于最早在20世纪60年代被提出。 关于进程有很多定义: ...

2018-03-07 · 1 min · 123 words

Python 字典

这一篇是《流畅的 python》读书笔记。主要介绍: 常见的字典方法 如何处理查不到的键 标准库中 dict 类型的变种 散列表的工作原理 泛映射类型 collections.abc 模块中有 Mapping 和 MutableMapping 这两个抽象基类,它们的作用是为 dict 和其他类似的类型定义形式接口。 标准库里所有映射类型都是利用 dict 来实现的,它们有个共同的限制,即只有可散列的数据类型才能用做这些映射里的键。 问题: 什么是可散列的数据类型? 在 python 词汇表(https://docs.python.org/3/glossary.html#term-hashable)中,关于可散列类型的定义是这样的: 如果一个对象是可散列的,那么在这个对象的生命周期中,它的散列值是不变的,而且这个对象需要实现 __hash__() 方法。另外可散列对象还要有 __eq__() 方法,这样才能跟其他键做比较。如果两个可散列对象是相等的,那么它们的散列只一定是一样的 根据这个定义,原子不可变类型(str,bytes和数值类型)都是可散列类型,frozenset 也是可散列的(因为根据其定义,frozenset 里只能容纳可散列类型),如果元组内都是可散列类型的话,元组也是可散列的(元组虽然是不可变类型,但如果它里面的元素是可变类型,这种元组也不能被认为是不可变的)。 一般来讲,用户自定义的类型的对象都是可散列的,散列值就是它们的 id() 函数的返回值,所以这些对象在比较的时候都是不相等的。(如果一个对象实现了 eq 方法,并且在方法中用到了这个对象的内部状态的话,那么只有当所有这些内部状态都是不可变的情况下,这个对象才是可散列的。) 根据这些定义,字典提供了很多种构造方法,https://docs.python.org/3/library/stdtypes.html#mapping-types-dict 这个页面有个例子来说明创建字典的不同方式。 >>> a = dict(one=1, two=2, three=3) >>> b = {'one': 1, 'two': 2, 'three': 3} >>> c = dict(zip(['one', 'two', 'three'], [1, 2, 3])) >>> d = dict([('two', 2), ('one', 1), ('three', 3)]) >>> e = dict({'three': 3, 'one': 1, 'two': 2}) >>> a == b == c == d == e True 除了这些方法以外,还可以用字典推导的方式来建造新 dict。 ...

2017-12-03 · 3 min · 568 words