一、心得体会
1、今天完成了什么?
20页镐头书
10个controller
-
回顾以前
- 什么是block?为什么要有block?
举个栗子:
class Project default :time Time.now end
这个时间就是服务器启动的时间,每次new project这个类的时候,时间是不变的。
class Project default_value_for :time { Time.now } end
- 什么是block?为什么要有block?
如果加个block ,就可以实现每次new的时候,时间是更新的。
- 抛出异常,用begin接
2、今天收获了什么?
- 文件的读写
- 线程和进程
- controller:客服手机、客服手机日志、客服日报(daylies)、客服手机目标(target)、客服手机发圈(moment)、朋友圈分类(momennt)、朋友圈日志、订单日志、订单服务日志
- 明天好好看看订单(找到个大boss)
- 没找到朋友圈图片
二、读书笔记
第十章 基本输入和输出(Basic Input and Output)
Ruby提供了两套乍看之下完全不同的I/O例程(routie)。第一套接口很简单——至今我们已经多次使用了。
print "Enter your name: "
name = gets
Kernel模块实现了一整套I/O相关的方法:gets、open、print、printf、putc、puts、readline,readlines和test,它们使得Ruby变成简便,这些方法通常对标准输入和输出进行操作,因而很适合用它们来编写过滤器。
第二种凡事是使用IO对象,这种凡事可以对IO进行更多的控制。
10.1 什么是IO对象(What Is an IO Object)
Ruby定义了一个IO基类来处理输入和输出,类File和BasicSocket都是该类的子类,虽然它们提供了更具体的行为,但是基本原则是相同的,IO对象是Ruby程序和某些外部资源之间的一个双向通道,当然,一个IO对象不止是这些显而易见的东西,但最终你所做的只是向之写入或者从中读取。
本章我们会重点介绍IO类和它最常用的子类File。如果想获得更多关于使用套接字类进行网络操作的信息。
对于那些想知道操作细节的人来说,这意味着一个IO对象可以管理操作系统的多个文件描述符,例如,如果你打开一个对管道,那么一个IO对象可以既包括读管道也可以包括写管道。
10.2 文件打开和关闭(Opening and Closing Files)
正如你所想,你可以使用File.new创建一个新的文件对象。
file = File.new("testfile", "r")
# .. 处理该文件
file.close
根据打开模式,你可以创建一个用来读、写或者兼有两者的文件对象(这里我们使用“r”模式打开testfile以从中读取数据)。我们也可以用“w”模式来表示写文件,而“r+”表示读写。
当创建一个文件时,你还可以指定文件的许可权限。打开文件后,我们可以写入或者读取所需的数据,最后,作为一个负责任的软件开发人员,我们需要关闭文件,以确保所有缓存的数据被写入一个文件,并释放所有相关的资源。
但Ruby能使这些操作更简单,方法File.open也可以打开文件,通常情况下,它和Tile.new行为相似,但是当和block一起调用时就有区别了,与返回一个新的File对象不同,open会以刚打开的文件作为参数调用相关联的block,当block退出时,文件会被自动关闭。
File.open("testfile, "r") do |file|
# .. 文件处理
end
第二种方式有额外的优势,在较前面的例子中,处理文件的过程中如果发生异常,可能file.close就不会被调用,一旦file变量出了其作用域,垃圾收集器最终会把它关闭,但可能一时半会儿不会发生,而与此同时,资源一直被占用。
以block调用File.open的形式没有这个问题,如果block内引发异常,那么在异常传递给调用者之前文件会被关闭。看似open方法实现了类似下面的代码:
class File
def File.open(*args)
result = f = File.new(*args)
if block_given?
begin
result = yield f
ensure
f.close
end
return result
end
end
10.3 文件读写(Reading and Writing Files)
我们用于”简单“I/O的所有方法都适用于文件对象,gets从标准输入读取一行(或者从执行脚本的命令行上指定的任意文件),而file.gets从文件对象file中读取一行。
例如,我们可以创建一个程序copy.rb
while line = gets
puts line
end
如果不带参数运行此程序,将从终端读取行然后再复制回终端显示,注意下一旦按下回车键,每行就会回显出来。
我们也可以从命令行传入一个或多个文件名,这种情况下gets将依次从文件中读入。
ruby copy.rb testfile
最后我们可以显式地打开文件,并从中读取数据。
File.open("testfile") do |file|
while line = file.gets
puts line
end
end
输出结果:
This is line one
This is line two
和gets一样,I/O对象还有一组额外的访问方法,它们使得Ruby编程更容易。
10.3.1 读取迭代器(Lterators for Reading)
你既可以使用普通的循环从IO流中读取数据,也可以使用各种各样的RUby迭代器来读取数据。
I/O#each_byte将以从IO对象中获取下一个8位字节的参数,调用相关联的block(在下面的例子中,对象类型是file)。
File.open("testfile") do |file|
file.each_byte {|ch| putc ch; print "."}
end
输出结果:
T.h.i.s
IO#each_line以文件的每一行为参数调用相关联的block。在下面的例子中,我们将用String#dump来显示换行符,这样一来你就知道我们没有骗人了。
File.open("testfile") do |file|
file.each_line { |line| puts "Got ${line.dump}" }
end
输出结果:
Got "This is line one\n"
Got "This is line two\n"
Got "This is line three\n"
Got "And so on...\n"
你可以将任意字符序列传递给each_line作为行分隔符,然后它会根据该字符序列相应地分割输入字符串,并返回分割后的每一行,这也是为什么在前面的例子中你能看到\n字符的原因,在下面的例子中,我们将使用字符e作为行分隔符。
File.open("testfile") do |file|
file.each_line("e") { |line| puts "Got #{ line.dump }" }
end
输出结果:
Got "This is line"
Got " one"
Got "\nThis is line"
Got " two\nThis is line"
Got " thre"
Got "e"
Got "\n And so on...\n"
如果你将迭代器思想和block自动关闭文件的特性结合在一起,你就获得了IO。foreach,这个方法以I/O数据源的名字作为参数,以读模式打开它,并以文件中的每一行作为参数调用相关联的迭代器,最后会自动关闭该文件。
IO.foreach("testfile") { |line| puts line }
输出结果:
另外,如果你喜欢,你还可以将整个文件的内容读取到一个字符串或者一行数组中。
读到字符串
str = IO.read("testfile")
str.length
str[0, 30]
读到一个数组中
arr = IO.readlines("testfile")
arr.length
arr[0]
不要忘了在不确定的世界里,I/O也必然是不确定的——大多数的错误会引发异常,你需要采取适当的行动以从错误中恢复。
10.3.1 写文件(writing to Files)
为什么读写不正常?gets卡住了
原来gets是输入函数,等待键盘输入的。
我也是被自己惊着了!!!
到目前为止,我们已经多次调用过puts和print,并传递任意已存在的对象作为参数,我们相信Ruby会对之进行正确的处理(当然,他也是这么做的)。但它到底做了什么呢?
答案很简单:除了少数几个例外,你传递给puts和prints的任意对象都会被该对象的to_s方法转换成一个字符串,如果由于某种原因,to_s方法未能返回一个合法的字符串,含有对象类名和ID的一个字符串就会被创建,类似于#<ClassName:0x123456>。
也有例外的情况,nil对象会输出字符串”nil“,而传递给puts的数组会依次被它的元素打印出来,就像每个元素被分别传递给puts一样。
如果你想写入二进制数据而不想被Ruby干扰怎么办?通常来说调用IO#print,并以包括写入字节的字符串作为参数。不过,你也可以使用底层的输入输出例程。
我们如何才能将二进制数据存储到字符串中呢?常用的3个方法:字符串的字面量,一个字节一个字节地的存入,或使用Array#pack。
str1 = "\001\002\003"
str2 = ""
str2 << 1 << 2 << 3
然而我怀念C++的iostream
有时实在无法计较个人的喜好......不过,就如你可以使用<<操作符添加一个对象到数组一样,你也可以添加对象到IO输出流。
endl = "\n"
STDOUT << 99 << " red balloons " << endl
输出结果:
99 red balloons
同样,方法<<使用to_s将它的参数转换成字符串,然后再按照它的方式传递。
尽管一开始我们鄙视可怜的<<操作符,但确实有一些使用它的理由。因为其他类(例如String和Array)也实现了相似语义的<<操作符,所以你可以使用<<编写代码来附加某些东西,而不必关心是添加数组、文件还是字符串。
这种灵活性也使得单元测试比较简单。
使用字符串I/O
很多时候,你需要处理假定读写一个或者多个文件的代码,但问题是:数据并不位于文件中,它或许是SOAP服务产生的数据,又或是从命令行传递来的参数,也可能你正在运行单元测试,而你并不想改变真正的文件系统。
来看看StringIO对象,它们的行为很像其他I/O对象,不同的是它们读写的是字符串而不是文件,如果你为读而打开一个StringIO对象,你需要提供一个字符串给它,在此StringIO对象上进行的所有读操作都会从该字符串中读。同样,当你想向StringIO对象写入时,你要传递一个待填充的字符串。
require 'stringio'
ip = StringIO.new("now is \nthe time\nto learn\n Ruby!")
op = StringIO.new("", "w")
ip.each_line do |line|
op.puts line.reverse
end
op.string
10.4 谈谈网络(Talking to Networks)
Ruby善于处理网络协议,无论底层协议还是高层协议。
对那些需要处理网络层的人来说,Ruby的套接字库提供了一组类,这些类让你可以访问TCP、UDP、SOCK、Unix域字节,以及在你的体系结果上支持的任意其他套接字节类型。
库中还提供了辅助类,这使得写服务器程序更容易,下面这个简答的例子使用finger协议获取本机机器上用户”mysql“的信息。
require 'socket'
client = TCPSocket.open('127.0.0.1', 'finger')
client.send("mysql\n", 0)
puts client.readlines
client.close
输出结果:
Login:mysql
Name:
Directory:/var/empty
Shell:
No Mail
No Plan
在较高的层次上,lib/net库提供了一组模块来处理应用层协议(目前支持FTP,HTTP,POP,SMTP和telnet)。
下面的程序将列出Progamatic和Programmer主页上显示的所有图片。
require 'net/http'
h = Net::HTTP.new('www', 80)
response = h.get('/index.html', nil)
if response.message == "ok"
puts response.body.scan(/<img src="(.*?)"/m).uniq
end
输出结果:
images/title_main.gif
尽管这个例子简单的令人着迷,但是还有很大的空间,特别是,它还没有做任何错误处理,它需要报告”Not Found“错误(声名狼藉的404),还需要能处理重定向(当web服务器为客户端请求的页面给出一个替代地址时)。
我们还可以更高层进行处理,通过装载open-uri库到程序中,方法Kernel.open立刻就可以识别文件名中的http://和ftp://等URL。不仅如此,它还自动处理重定向。
require 'open-uri'
open('http://www.pragmaticprogrammer.com') do |f|
puts f.read.scan(/img src="(.*?)"/m).uniq
end
第11章 线程和进程(Threads and Processes)
Ruby给了你两种基本的方式去组织程序,这样你可以”同时“运行程序的不同部分。使用多线程可以在程序内部把相互协作的任务分开,或者可以使用多进程将任务分解到不同的程序。
11.1 多线程(Multithreading)
通常同时做两件事最简单的方式是使用Ruby线程,这些线程完全在进程内部,并在Ruby解释器内部实现,这使得Ruby线程是彻底可移植的——它们不依赖于操作系统。当然,同时也无法获得本地线程(native thread)所带来的某些益处。这指的是什么?
你可能体验过线程饿死(低优先级的线程得不到机会运行),如果把线程搞死锁了,整个进程可能被折磨的宕掉(halt)。如果一个线程碰巧进行了一个需要长时间完成的操作系统调用,所有线程会挂起直到解释器重新得到控制为止。最后,如果机器有多个处理器,则Ruby线程利用不了这个事实——因为它们运行在一个进程内、并且是单独一个的本地线程内,它们被约束每次只能运行在一个处理器上。
所有这一切听起来挺吓人的,实际上,在很多情况下使用线程的益处远远超出了可能会出现的任何潜在困难,Ruby线程是一种有效的轻量级的方法,他可以使代码达到并行化。只需要理解这些底层的实现问题并依次进行设计!
11.1.1创建Ruby线程(Creating Ruby Threads)
创建新的线程非常直接。下面这段代码是一个简单的例子,它并行地下载了一组网页,对要求下载每个的URl,这段代码需创建一个单独的线程去处理这个HTTP事务。
require 'net/http'
pages = %w(www.google.com www.baidu.com)
threads = []
for page_to_fetch in pages
threads << Thread.new(page_to_fetch) do |url|
h = Net::HTTP.new(url, 80)
puts "Fetching: #{url}"
resp = h.get('/', nil)
puts "Got #{url}: #{resp.message}"
end
end
threads.each {|thr| thr.join}
输出结果:
Fetching:www.rubycentral.com
Fetching:
让我们更仔细地看看这段代码,因为期间发生了一些微妙的事情。
使用Thread.new调用来创建新线程,为它提供一个含有将要在新线程中运行的block。在这个例子中,block使用net/http库取回我们指定的每个网站的主页。我们跟踪信息清楚地的显示了这些获取正在并行地进行。
创建线程时,把所需的URL作为参数传递给Thread.new。这个参数作为url变量被传递给block。我们为什么这么做,而不是简答地在block里直接使用page_to_fetch变量的值呢?
线程共享在它开始执行时就已经存在的所有全局变量、实例变量和局部变量中。就像任何一个有儿时兄弟的人可能告诉你的那样,分享并不总是一件好事情,在这个例子中,所有3个线程会共享page_to_fetch变量,当地一个线程开始运行时,page_to_fetch被设置为”www.rubycentralc.om“,同时创建线程的循环仍然运行着,在第二个回合时,page_to_fetch被设置为”slashdot.org“。如果使用page_to_fetch变量的第一个线程还没有结束,它会突然开始使用新的值。很难发现这种类型的错误。
不过,在县城block里创建的局部变量对线程来说是真正的局部变量——每个线程会有这些变量的私有备份。
在这个例子中,url变量会在每次线程创建时被设置,因而每个线程都有自己的网页地址的备份。可以通过Thread.new传递任意个参数到block中。
操作线程
另外一个微妙的事发生在我们下载程序的最后一行,为什么要对创建的每个线程调用join呢?
当Ruby程序终止时,不管线程的状态如何,所有线程都被杀死,当然可以通过调用线程的Thread#join方法来等待特定线程的正常结束,调用join的线程会阻塞,直到指定的线程正常结束为止。
通过对每个请求者线程调用join,可以确保所有3个请求会在主程序终止之前结束,如果不想永远阻塞,可以给予join一个超时参数——如果超时在线程终止之前到期,join返回nil。join的另一个变种,Thread#value方法返回线程执行的最后语句的值。
除了join之外,另有一些便利函数用来操作线程,使用Thread.current总是可以得到当前线程,可以使用Thread.list得到一个所有线程的列表,返回包含所有可运行或被停止的线程对象。可以使用Thread#status和Thread#alive? 去确定特定线程的状态。
另外,可以使用Thread#priority=调整线程的优先级。更高优先级的线程会在低优先级的线程前面运行,我们稍后就会在更多地讨论线程调度、停止和启动线程。
线程变量
线程通常可以访问在它创建时其作用范围内的任何变量,线程block里面的局部变量是线程的局部变量,它们没有被共享。
但是如果需要线程局部变量能被别的线程访问——包括主线程,怎么办?Thread类提供了一种特别的设施,允许通过名字来创建和访问线程局部变量。可以简单地把线程对象看作一个散列表,使用[]=写入元素并使用[] 把它们读出。
在下面的例子中,通过键mycount,线程把count变量的当前值记录到线程局部变量中,在读取时,代码使用字符串”mycount“对线程对象进行索引(这里存在一个镜态条件(race condition),但是因为我们还没有讨论同步,所以闲杂只是悄悄忽略它)。
竟态条件出现在当两段或多段代码(或硬件)都试图访问一些共享资源时,在这里结果会随着它们执行的次序而改变,在这个例子中,有可能一个县城将其mycount变量的值设置给count,但是在它有机会增加count之前,这个县城被调度了出去,而另外一个线程重用了相同的值,这个问题可以通过对共享资源的访问(如count变量)进行同步解决。
count = 0
threads = [ ]
10.times do |i|
threads[i] = Thread.new do
sleep(rand(0.1))
Thread.current["mycount"] = count
count += 1
end
end
threads.each { |t| t.join; print t["mycount"], ", " }
puts "count = #{count}"
主线程等待子线程结束,然后打印出每个子线程获得count的值。纯粹是为了让它变得更有趣些,在记录这个值之前,我们让每个线程都随机等待了一段时间。
11.1.2 线程和异常(Threads and Exceptions)
如果线程引发了(raise)了未处理的异常,会发生些什么呢?依赖于abort_on_exception标志和解释器debug标志的设置。
如果abort_on_exception是false,debug标志没有启用(默认条件),未处理的异常会简单地杀死当前线程——而所有其他线程继续运行,实际上,除非对所引发这个异常的线程调用了join,你甚至根本不知道这个异常存在。
在下面的例子中,线程2自爆了,同时没有任何输出,但是你仍然可以从其他线程看到蛛丝马迹。
threads = [ ]
4.times do |number|
threads << Thread.new(number) do |i|
raise "Boom!" if i==2
print "#{i}\n"
end
end
threads.each {|t| t.join}
输出结果:
0
1
3
in `block (2 levels) in irb_binding'
当线程被join时,我们可以rescue这个异常。
threads = [ ]
4.times do |number|
threads << Thread.new(number) do |i|
raise "Boom!" if i == 2
print "#{i}\n"
end
end
threads.each do |t|
begin
t.join
rescue RuntimeError => e
puts "Failed: #{e.message}"
end
end
结果:
0
1
3
Failed: Boom!
但是,设置abort_on_exception为true,或者使用-d选项打开debug标志,未处理的异常会杀死所有正在运行的线程。一旦线程2退出,不会产生更多的输出。
Thread.abort_on_exception = true
threads = [ ]
4.times do |number|
threads << Thread.new(number) do |i|
raise "Boom!" if i == 2
print "#{i}\n"
end
end
threads.each {|t| t.join}
结果:
0
1
Boom!
这段代码也说明了一个很容易犯错的地方(gotcha)。在循环里面,线程使用print而不是puts去输出数字,为很么呢?这是疑问,在幕后puts的工作被分为两部分:输出其参数,然后再输出回车换行符。
在这两个动作之间,一个线程可能得到调度,这样它们的输出可能会交织在一块。
用已经包含回车换行符的字符串作为参数调用print则规避了这个问题。
11.2 控制线程调度器(controlling the Thread Scheduler)
在设计良好的程序中,你通常只是让线程做它们该做的事情:多线程程序中建立时间依赖性(timing dependency)通常被认为是糟糕的设计,因为这会导致代码复杂化的同时阻碍线程调度器优化程序的执行。
但是有时候需要显式地控制线程,也许点唱机有一个显示光束的线程。音乐停止时需要暂时停止它。因而这两个线程可能处于一种经典的生产者-消费者关系中,如果生产者的订单没有完成,则消费者必须暂停。
Thread类提供了若干种控制线程调度器的方法,调用Thread.stop停止当前线程,调用Thread#run安排运行特定的线程,Thread.pass把当前线程调度出去。允许运行特别的线程,Thread#join和Thread#value挂起调用它们的线程,直到指定的线程结束为止。
下面的程序说明了这些特性,它创建了t1和t2两个子线程,每个子线程运行Chaser类的一个实例,chaser方法count加1,但不会让它比另外一个线程里的count值多两个以上。
为了防止差距大于2,这个方法调用了Thread.pass,它允许线程中的chaser赶上来。
为了变的有趣些,我们一开始就让这些线程把自己挂起来,然后随机地启动其中一个线程。
class Chaser
attr_reader :count
def initialize(name)
@name = name
@count = 0
end
def chase(other)
while @count < 5
while @count-other.count > 1
Thread.pass
end
@count +=1
print "#@name: #{count}\n"
end
end
end
c1 = Chaser.new("A")
c2 = Chaser.new("B")
threads = [
Thread.new { Thread.stop; c1.chase(c2) },
Thread.new { Thread.stop; c2.chase(c1) },
]
start_index = rand(2)
threads[start_index].run
threads[1-start_index].run
threads.each { |t| t.join }
但是在实际的代码中使用这些原语(primitive)实现同步并不是一件容易的事情——竞态条件总是等着趁机咬你一口。同时处理共享数据时,竞态条件总是会带来漫长且令人沮丧的调试阶段。实际上,前面例子包含了这样的错误:有可能在一个线程中,count被增加了,但是在count被输出之前,第二个线程得到调度并且输出了count。这个输出结果将会是乱序的。
值得庆幸的是,线程有辅助的设施——互斥的概念,使用互斥,可以实现许多安全的同步机制。
11.3 互斥(Mutual Exclusion)
这是最底层的阻止其他线程运行的方法,它使用了全局的线程关键(thread-critical)条件,当条件设置为true(使用Thread.critical=方法)时,调度器将不会调度现有的线程取运行,但是这不会阻止创建和运行新线程。
某些线程操作(如停止或杀死线程,在当前线程中睡眠和引发异常)会导致及时线程处于一个关键区域内也会被调度出去。
直接使用Thread.critical=当然是可能的,但是它不是很方便,实际上,我们强烈建议不要使用它,除非你是一个多线程变成(和喜欢长时间调试)的黑带高手(black belt)。值得庆幸的是,Ruby有很多变通方法,马上会看到其中一种,Monitor库,你也许想看一下Sync库,Mutex库,和现在的thread库总的Queue类。
11.3.1 监视器(Monitor)
尽管这些线程原语提供了基本的同步机制,但是熟练使用它们需要很多技巧,这些年来,各种人提出各种高级别的替代方法,尤其在面向对象系统中,其中比较成功的是一个方法是监视器(Monitor)的概念。
监视器用同步函数对包含一些资源的对象进行封装,为了看看在实际如何用他们。考察一个被两个线程访问的简单计数器。