一、心得体会
1、今天主要完成了什么?
- 今天主要看了互斥、运行多个线程、Ruby调试器、Ruby基础三章
- 10个controller
2、今天主要收获了什么?
- 10个controller:order、order_change、order_service_change、payment、problems(疑难)、工序、工序日志(process_change)、属性(property)
- 假线程
- 全局锁
- 非Ruby锁的等待是读缓存、数据库、第三方,不会全局锁 Ruby等
- 一段代码只运行一个cpu
- 操作系统 运算器
- 很多事情不是纯计算的,网络传输也需要有时间,磁盘、内存、总线、cpu经常需要等待其他“队友”完成任务才能继续运算,效率很低。
- 时间片轮转 按时间片分开 (进程、线程、前程(fibel))
- 当任务足够多的时候,就需要锁的出现,
- 单独的资源,数据磁盘地址,标记,地址1,private
- private(操作系统里的锁)
3、今天犯了哪些错误?
4、明天还需要做哪些工作?
- 明天争取看3章
二、读书笔记
第11章 线程和进程
11.3 互斥(Mutual Exclusion)
这是最底层的阻止其他线程运行的方法,它使用了全局的线程关键(thread-critical)条件,当条件设置为true(使用Thread.critical=方法)时,调度器将不会调度现有的线程取运行,但是这不会阻止创建和运行新线程。
某些线程操作(如停止或杀死线程,在当前线程中睡眠和引发异常)会导致及时线程处于一个关键区域内也会被调度出去。
直接使用Thread.critical=当然是可能的,但是它不是很方便,实际上,我们强烈建议不要使用它,除非你是一个多线程变成(和喜欢长时间调试)的黑带高手(black belt)。值得庆幸的是,Ruby有很多变通方法,马上会看到其中一种,Monitor库,你也许想看一下Sync库,Mutex库,和现在的thread库总的Queue类。
11.3.1 监视器(Monitor)
尽管这些线程原语提供了基本的同步机制,但是熟练使用它们需要很多技巧,这些年来,各种人提出各种高级别的替代方法,尤其在面向对象系统中,其中比较成功的是一个方法是监视器(Monitor)的概念。
监视器用同步函数对包含一些资源的对象进行封装,为了看看在实际如何用他们。考察一个被两个线程访问的简单计数器。
class Counter
attr_reader :count
def initialize
@count = 0
end
def tick
@count += 1
end
end
c = Counter.new
t1 = Thread.new { 100_000.times { c.tick } }
t2 = Thread.new { 100_000.times { c.tick } }
t1.join
t2.join
c.count
也许你会惊讶,count不等于200 000。下面一行是罪魁祸首。
@count +=1
这一行实际上比看起来复杂的多,Ruby解释器会把它分成:
val = fetch_current(@count)
add 1 to val
store val back into @count
想象一下如果两个线程同时执行这段代码。
尽管,基本的load/add/store指令集被执行了5次,可是最终的count是3,这是因为线程1在中间中断了线程2的执行,当线程2恢复运行时,它把一个不新鲜的值保存到@count中。
下表展示的是t1和t2的竞态过程
t1: val = fetch_current(@count) @count=0
t1: add 1 to val 0
t1: store val back into @count @count = 1
t2: val = fetch_current(@count) 1
t2: add 1 to val 1
t2: store val back into @count = 2
解决方法:使用监视器
class Counter
attr_reader :count
def initialize
@count = 0
end
def tick
synchronize do
@count += 1
end
end
end
c = Counter.new
t1 = Thread.new { 100_000.times { c.tick } }
t2 = Thread.new { 100_000.times { c.tick } }
t1.join
t2.join
c.count
11.4 运行多个进程(Running Multiple Processes)
11.4.1 衍生新进程(Spawing New Processes)
11.4.2 独立子进程(Independent Children)
11.4.3 bock和子进程(Blocks and Subprocesses)
第13章 当遇到麻烦时
13.1 Ruby调试器
Ruby自带一个调试器,并被集成到Ruby的基本系统中,你可以通过在调用解释器时使用 -r debug选项、连同Ruby的其他选项以及脚本名称,来运行调试器。
Ruby -r debug [调试选项] [程序文件] [程序参数]
调试器支持你所期望的常用功能,包括设置断点、步入或者跳过方法调用以及显示调用栈和变量。
它还能列出某个对象或类中定义的实例方法,也能列出并控制RUby中单独的线程。
如果你在安装Ruby时启用了readline功能,那么就可以用方向键在命令的历史记录中来回选择,还可以使用行编辑命令来修正前面的输入。
ruby -r debug test.rb
Debug.rb
Emacs support avaliable
t.rb
13.2 交互式Ruby
irb可以创建子会话,每个会话都有上下文,例如,你可以创建一个和原始会话有相同上下文的子会话,也可以在某个类或实例的上下文中创建子会话。
- jobs
- session
- fg 0
13.3 编辑器支持
Ruby解释器被设计成一次读一个程序:这意味着你可以通过管道把整个程序作为标准输入传递给解释器,而它就可以很好地的工作。
13.4 但是它不运作
如果你在写自己的Ruby程序时,遇到问题,不妨试试下面这些方法:
- 首先也是最重要的是:运行你的脚本时启用警告(-w命令行选项)
- 如果碰巧忘记了参数列表中的“,”——特别是用于输出时——你会招致一些非常奇怪的错误信息。
- 源代码最后一行的解析错误经常是由于某处遗忘了end关键字造成的,有时是很前面的地方。
- 没有调用属性设置方法,在类定义中,setter=被解释为局部变量的赋值语句,而不是方法调用。如果使用self.setter=形式则表示方法调用。
class Incorrect
attr_accessor :one, :two
def initialize
one = 1
self.two = 2
end
end
obj = Incorrect.new
obj.one -> nil
obj.two -> 2
- 对象看起来没有被正确设置,通常是由于initialize方法拼写错误造成的。
class Incorrect
attr_accessor :answer
def ww # 写错函数名
@answer = 42
end
end
obj = Incorrect.new
obj.answer -> nil
- Block的参数和局部变量处于相同的作用域中,当执行block时,如果block的参数存在已定义的同名局部变量,那么该变量可能被block修改,这可能是优点也可能不是。
c = "carbon"
i = "io"
elements = [c, i]
elements.each_with_index do |element, i|
#
end
- 注意优先级问题,特别是当使用{}而不是用 do/end时。
def one(arg)
if block_given?
"block given to one's returns #{yield}"
else
arg
end
end
def two
if block_given?
"block given to 'two' returns #{yield}"
end
end
result1 = one two {
"three"
}
result2 = one two do
"three"
end
puts "With braces, result = #{result1}"
puts "With braces, result = #{result2}"
输出端可能被缓存,这意味着你可能无法立即看到输出的信息,另外,如果你同时向$stdout和$stderr输出信息,那么输出的顺序与你期望的可能有所不同,始终用非缓冲的I/O来处理调试信息(设置sync=true)
若数字来路不正规,则它们实际上可能是字符串,从文件中读取的文本是String,Ruby不会自动把它转换成数字,调用Integer会进行转换(如果输入的不是合法的整数形式将会产生异常)。Perl程序员常犯的一个错误是:
while line = gets
num1, num2 = line.split(/,/)
# ...
end
你可以重写为:
while line = gets
num1, num2 = line.split(/,/)
num1 = Integer(num1)
num2 = Integer(num2)
# ...
end
或者你可以使用map来一次性转换所有的字符串。
while line = gets
num1, num2 = line,split(/,/).map {|val|, Integer(val) }
# ...
end
- 无意的别名——如果你使用一个对象作为散列表的关键字,确保它不会改变其hash值(或者改变后调用Hash#rehash)
arr = [1, 2]
hash = { arr => "value" }
hash[arr]
arr[0] = 99
hash[arr] = nil
hash.rehash
hash[arr]
确保你所使用对象的类是你所预想的,如有疑虑,则使用puts my_obj.class查看。
确保方法名字以小写字母开头,而类名和常量名以大写字母开头。
如果方法调用结果与所期望的不符,确保将参数放到小括号内部。
确保方法参数列表的左括号紧跟在方法名后,且中间没有空格。
使用irb和调试器。
使用Object#freeze。如果你怀疑某段未知的代码会将变量设置为一个虚妄的值,则试着冻结该变量,那么当罪魁祸首试图改变此变量时就可以抓住它。
使得编写Ruby代码既容易又有趣的一项主要技术是:渐增式地开发应用程序。写几行代码,运行之,还可以使用Test::Unit写一些测试。接着写更多的代码,并运行之它们。
动态类型语言的一个主要好处是你不必等到代码完善之后才运行它们。
13.5 然而它太慢了
Ruby是一门解释性的高级语言,因此它可能没有低级语言例如C的运行速度快,下面小节中,我们试着学习一些提高性能的基本技术。
通常,运行速度慢的程序会有一两个性能瓶颈,它们浪费了执行时间,找到并改进这些瓶颈,将使得整个程序的效率立即得到改善,难点在于如何找到它们,Benchmark模块和Ruby的剖析器(profiler)可以帮助我们。
13.5.1 Benchmark
我们想知道一个大的循环是使用循环内的局部变量速度快,还是使用外部已存在的变量速度快。
使用benchmark时需要很小心,因为垃圾回收常常会导致Ruby程序运行速度变慢。
垃圾回收可能在程序运行的任何时候发生,因此benchmark可能会给你错误的结果:banchmark显示一段代码运行很慢,而实际上速度变慢是由于执行该代码时垃圾回收恰好被触发造成的。
Benchmark模块的bmbm方法会运行测试两次,一次作为预演,一次实际测量性能,力图将垃圾回收带来的影响降到最低,Benchmark测试过程本身相对设计的比较优雅——它不会使程序运行慢太多。
第14章 Ruby和Ruby世界
14.1 命令行参数(Command-Line Arguments)
“In the beginning was the command line”(最初始于命令行)。无论Ruby部署在什么系统上,不管它是超级高端计算图形工作站或是嵌入式PDA设备,我们总是要运行Ruby解释器的,所以有机会将命令行参数传递给它。
Ruby命令行由3部分组成:传递给Ruby解释器的选项,要运行的程序名称(可选)和程序的一组参数(可选)。
如果命令行上没有指定文件名,或者指定的文件名是单个连字符(-),Ruby会从标准输入读入程序源码。
程序的参数跟在程序名称后面,例如,
ruby -w - "Hello World"
Ruby解释器会启用警告,从标准输入读入程序,同时把“Hello World”字符串作为程序的参数传递给程序。
14.1.1命令行选项(Command-Line Options)
-0[八进制]
标志0(数字零)指定记录分隔字符(如果后面没有跟其他数字,则分隔符是\0)。
-777 一次性读取整个文件(因为它是非法字符)。设置$/全局变量。
14.1.2 ARGV
程序文件后面出现的任何命令行参数在Ruby程序中可用,它们保存在全局数组ARGV中。比如,假设test.rb包含如下程序:
ARGV.each { |argv| p arg }
用下面的命令行调用它:
ruby -w test.rb "Hello World" a1 1.6180
会产生如下输出:
"Hello World"
"a1"
对于所有C程序员来说这里有一个经常出错的问题(gotcha)——ARGV[0]是程序的第一个参数而不是程序名称,当前程序的名称位于全局变量$0中,注意到ARGV中所有的值都是字符串。
如果你的程序试图从标准输入(或者使用特别文件ARGF)中读取,ARGV中的程序参数会被当做文件名称,然后Ruby会从这些文件中读取,如果程序把参数和文件名称混合在一起,那就确保从这些文件读取之前已清空了ARGV数组中的非文件名参数。
14.2 程序终止(Programming Termination)
Kernel#exit方法会终止程序,返回一个状态值给操作系统,但是与有些语言不一样,exit没有立即终止程序,Kernel#exit首先抛出可以捕获的SystemExit异常,然后执行若干个清理动作,其中包括运行任何已注册的at_exit方法和对象的终止方法(finalizers)。
14.3 环境变量
ENV['SHELL'] -> "/bin/sh"
ENV['HOME'] -> "/Users/dave"
ENV['USER'] -> "dave"
ENV.keys.size
ENV.keys[0, 7]
14.3.1 写入环境变量(Writing to E女irenment Variables)
Ruby程序可能写入ENV对象,在大多数系统上这会改变相应环境变量的值,当然,这种变化仅限于做出这种改变的进程,以及随后被它创建的那些子进程中,下面的代码说明了环境变量的继承,一个子进程改变一个环境变量,这种变化会被它随后产生的一个进程继承,但是,父进程看不到这种变化(这正好证明了:父母永远无法真正知道孩子在做什么)
14.4 从何处查找它的模块
使用require或load把程序库模块装入程序中,有些模块是Ruby自带的,有些可能是从Ruby应用归档(Ruby Application Archive)安装的,有些可能是自己开发。
那么,Ruby如何找到它们呢?
为特定机器编译Ruby时,它预定义了一组标准目录去保存Ruby程序库,在哪里保存它们是和所讨论的机器相关的,可以使用类似下面的这个命令得到答案:
ruby -e 'puts $:'
在典型的Linux机器上,你可能会发现下面的一些目录,注意Ruby1.8中的目录的顺序已经发生改变——体系结构相关的目录跟在与机器无关的目录后面。
/usr/local/lib/ruby/site_ruby/1.8
site_ruby目录为了保存已经添加的那些模块和扩展,与体系结构相关的目录(这个例子中的i6886-linux)保存特定于这种机器的可执行文件和其他东西,所有这些目录会自动被保存。
14.5 编译环境
为特定的体系结构编译Ruby时,所有用来编译它的相关设置(包括用来编译Ruby的机器的体系结构,编译器选项和源代码目录等等)都被写入到库文件rbconfig.rb中的congfig模块,Ruby安装之后,任何Ruby程序可以使用这个模块得到编译Ruby的细节。
require 'rbconfig'
include Config
CONFIG["host"]
CONFIG["libdir"]
扩展库可以使用这个配置文件在给定的体系结构上正确地编译和链接,
第15章 交互式Ruby Shell
注意返回值nil,它们分别是定义方法和运行方法的结果,这个方法输出Fibonacci(斐波那契)数列,然后返回nil。
irb的一个重要用途在于,体验你刚刚编写的代码,可能你想追踪一个bug,或者你只是想把玩一下。如果你将程序加载到irb中,就可以创建它定义的类的实例并调用其方法,例如,文件code/fib_up_to.rb包括了下面的方法定义。
def fib_up_to(max)
i1, i2 = 1, 1
while i1 <= max
yield i1
i1, i2 = i2, i1+i2
end
end
我们可以把它加载到irb中,并尝试调用这个方法。
15.1.1 Tab补全(Tab Completion)
如果你的Ruby支持readline,可以使用irb的完成功能,在加载之后,当你的提示符后键入表达式时,“完成功能”改变了Tab键的含义。当你在词的中间按下tab键时,irb会查找所有在此刻有意义的候选完成,如果只有候选,irb将会自动填充它,如果有多于一个选择,则irb最初什么也不做。
不过,如果你再次按下tab键,它会显示一个当前有效的完成列表。
例如,如果你在一个irb会话中,刚刚将一个字符串对象赋值给变量a。
你现在想尝试调用这个对象的String#reverse方法,你开始键入a.re然后按下tab两次。
irb列出了该对象所支持的名字以“re”开头的全部方法,我们看到了想要的那个,reverse,并输入它名字的下一个字符,按下tab键。
Tab补齐是以一个扩展库来实现的,即irb/completion。当调用irb时,你可以从命令行加载它。
如果你总是使用tab补齐,最快捷的方式可能是将require命令放到你的.irbrc文件中。
15.1.2 子会话(Subsession)
irb支持多个、并发的会话,当前会话只有一个:其他的在被激活前处于休眠状态。在irb内输入irb命令会创建一个子会话,输入jobs命令列出所有的会话,输入fg则激活一个特定的休眠会话。