docker防止fork炸弹

在开发过程中,我们使用了docker作为容器来进行编程语言代码的评测,以用于防止有害代码破坏宿主物理机器,但是在昨天发现docker还是会受fork炸弹的影响将宿主物理机器挂掉。针对这个问题,我在docker官网找到了相关的解决方法,记录分享并以备之后再次遇到此类问题可以很快找到解决方案。
当前解决方案只适用于docker1.1版本及之后(通过docker.io进行安装,sudo apt install docker.io)。
解决方案的思想就是:在启动docker的时候给其加上启动选项–pids-limit来限制cgroup中的fork的进程的最大数,我这里限制的30,可以根据自己的需求去限制。

1
docker run -it --pids-limit 30 Ubuntu:14.04

上述方法即可防止fork炸弹。
以下内容转载自http://blog.csdn.net/thinkhy/article/details/50995720

Docker容器与安全

Docker能否大规模用于生产环境,尤其是公有云环境,就在于Docker是否能提供安全的环境。本文将总结《Docker容器与容器云》一书3.9节『Docker与容器安全』的主要内容,包括Docker现有安全机制、存在的安全问题以及Docker安全增强三个方面。

1. Docker的安全机制

1.1 Docker daemon安全

  • Docker向外界服务提供了四种通信方式,默认是以Unix域套接字的方式来与客户端通信,这种方式较TCP形式更为安全。
  • Docker也提供了TLS传输层安全协议,通过–tlsverify(安全传输校验),–tlscacert(信任证书)、–tlskey(服务器或者客户端秘钥)、–tlscert(证书位置)来配置。

    1.2 镜像安全

  • Docker registry镜像库访问控制

    • Docker daemon第一次启动时,通过公网(Amazon CDN)载入official.json包,饮食公共image和用户image的目录以及数字签名信息。
    • official.json在Docker daemon启动时加载到MemoryGraph,MemoryGraph用于存储公钥以及命名之间的授权映射,默认授权节点对授权空间有读写权限。
  • 镜像校验和

    • 镜像校验和用来保证镜像的完整性,以预防可能出现的镜像破环。
    • 目前Docker对于镜像校验和和验证失败不采取任何措施,仅输出警告信息

      1.3 内核安全

  • 内核为容器提供了两种技术cgroup和namespace,分别对容器进行资源限制和资源隔离。
    • 容器本质是进程,cgroup用来限制容器的资源使用量,避免单个容器耗尽系统资源。
  • namespace用来隔离容器与宿主机,以及不同的容器。
    • Docker目前仅完整支持uts、ipc、pid、network、mount这5种ns,user ns尚未完全支持。
    • 系统资源未进行隔离,如/proc,/sys、SELinux、time、syslog、/dev设备信息等均未进行隔离。

      1.4 容器之间的网络安全

  • Docker可通过iptabls设定规则实现禁止或允许容器之间的通信。

    1.5 Docker容器Capability限制

  • 容器的行为通过Linux超级用户分组限制,具体包括了CHOWN、DAC_OVERRIDe、FSETID、FOWNER、MKNOD、NET_RAW、SETGID、SETUID、SETFCAP、SETPCAP、NET_BIND_SERVICE、SYS_CHROOT、KILL和AUDIT_WRITE。
  • Docker进程的Capability可通过docker run命令的参数进行配置

    2. Docker安全问题

    2.1 磁盘资源限制问题

  • Docker容器通过镜像层叠的方式来构建容器内的文件系统,本质上还是在宿主机文件系统的目录(/var/lib/docker)下存储文件。
  • 极有可能出现一个容器将宿主机上所有的磁盘空间耗尽,导致其它容器无法存储文件,所以有必要对容器的磁盘使用量进行限制。

    2.2 容器逃逸问题

  • Docker使用操作系统进行虚拟化,共享内核、内存、CPU以及磁盘,易造成容器逃逸问题。
  • Docker1.0之后采用白名单来限制容器的能力,会给出默认的容器Capability清单,禁止容器拥有清单之外的Capability。

    2.3 容器DoS攻击与流量限制问题

  • 公有云基于虚拟化技术实现,攻击数据包可能不需要通过物理网卡就可以攻击同一个宿主机下的其他容器,传统Dos预防措施无法适用容器之间的攻击。
  • 的Docker容器连接在网桥上,通过veth pari技术创建网卡,其一端在容器内命名为eth0,另一张网上驻留在宿主机环境之中。
  • 同一宿主机下所有容器共用一张物理网卡,如果一个容器抢占大部分带宽,会影响其它容器使用。

    2.4 超级权限问题

  • docker run时加入–privileged参数能使容器获得所有的超级用户权限能力,并将所有的宿主机的所有设备挂载到容器内。

    3. Docker安全的解决方案

    3.1 SELinux

  • SELinux三种控制方式
    • Type Enforcement: 主要的访问控制机制。
    • Role-Based Access Control(RBAC):基于SELinux用户的权限控制手段。
    • Multi-Level Security(MLS): 多级分类安全,指定level标签。
  • 为什么要在Docker中使用SELinux
    • SELinux将所有进程和文件打上标签,而容器以进程方式运行,所以控制进程如何访问资源,也就是限制容器如何去访问资源。
    • SELinux策略是全局的,它不是针对具体用户设定,而是强制整个系统遵循。
    • 减少提权攻击风险。

      3.2 user namespace

  • 容器的超级用户权限通过ns映射到宿主机是一个普通用户。
  • 容器被恶意程序攻击,所做的也就是这个普通用户的权限,而非宿主机的超级权限。

    3.3 磁盘限额

  • Docker仅对Device Mapper文件系统的限额提供了–storage-opt参数进行限制。
  • cgroup没有对磁盘进行限制,Linux磁盘限额技术主要基于用户和文件系统。
  • 可能的解决方案
    • 所有用户共有宿主机的一块磁盘,限制用户在磁盘上的使用量来限定容器的磁盘使用量。
    • 选择支持目录限额的文件系统,如XFS。
    • Docker定期检查每一个容器磁盘使用量,会对性能造成影响。
    • 创建虚拟文件系统,些文件系统仅供某一个容器使用。

      3.4 容器流量限制

  • Docker没对容器的网络带宽做限制。
  • 可以采用Traffic Controller容器对容器网卡流量进行限制,一定程序上减少容器Dos攻击危害。

    3.5 GRSecurity内核安全增强工具

  • Docker容器共享宿主机的内存,在内存安全上存在不少问题,需要针对内存破坏做防御。
  • GRSecurity是一个对内核的安全扩展,通过智能访问控制来阻止内存破坏,预防0day漏洞。

    3.6 fork炸弹

  • fork炸弹以极快速度创建大量进程,以此消耗系统资源,使系统无法运行新程序,现有进程运行速度放缓。
  • 容器本身在内核层面隔离性不足,fork bomb会给容器带来灾难性影响。
  • fork bomb受到社区关注(Issue 6479),但目前还没有完美解决方案。
  • Docker无法使用ulimit来限制forkbomb问题,因为一个宿主机用户可能同时启动多个容器,无法对每个容器做进程数的限制。
  1. 总结
    Docker自身已经提供了不少安全机制,但Docker目前仍然只适于运行可信应用程序(内部使用),如果需要运行任意代码,安全很难得到保证。在日常应用中,还可以通过SELinux、GRSecurity、seccomp等工具来增强容器安全。

Comment and share

Linux time命令详解

参考:http://blog.he96.com/2011/01/linux-timewhat-do-real-user-and-sys.html

real,user,sys

我们常用linux中的time命令计算某个程序的运行耗时,用户态CPU耗时,系统态COU耗时。
例如:

1
2
3
4
time foo
real 0m0.020s
user 0m0.020s
sys 0m0.000s

现在我们探讨一下上述三个时间分别代表的含义:

  • real: 表示a.out程序的实际的运行耗时,也就是在a.out运行开始时刻你看了一下手表,a.out运行结束时刻看了一眼手表,两次时间的差值就是real的值。例如使用time sleep 2的时候real值为2
  • user: a.out运行在用户态CPU的时间
  • sys: a.out运行在核心态CPU的时间

用户态和核心态

然后我们讲一下用户态和核心态:

  • 核心态(kernal mode): 在内核态,代码拥有完全的,不受任何限制的访问底层硬件的能力。可以执行任意的CPU指令,访问任意的内存地址。内核态通常情况下,都是为哪些最底层的,由操作系统提供的,可信可靠耳朵代码来运行的。内核态崩溃将是灾难性的,它会影响到整个系统。
  • 用户态(User mode):在用户态,代码不具备直接访问底层硬件或者内存的能力,而必须借助操作系统提供的可靠的,底层的API来访问硬件或者内存。由于这种隔离带来的保护作用,用户态代码崩溃,操作系统可以正常恢复。我们大多数代码是运行在用户态。

区分内核态和用户态的作用:隔离保护,使得系统更稳定。
使用这三个时间我们可以计算当前程序CPU的使用率:
CPU Usage = (user + sys) / real_time

常见误区

  • realtime = user + sys
  • realtime > user + sys
    由于有一些程序需要等待I/O等导致realtime与user+sys不相等
    当在多核CPU的情况下第二种情况不成立

Comment and share

C++前置声明

in C++学习

C++前置声明

以个人理解,C++的前置声明的意思是在类定义之前对其进行声明。它在现实编程的场景中可以解决很多问题。比如解决两个类相互依赖的问题,降低类之间的编译依存关系等等。

实际场景

我们分别定义一个锁(Lock)和钥匙(Key)的类(默认一把钥匙只能开一把锁,一把锁只能被一把钥匙打开)。代码如下:
Lock.hpp

1
2
3
4
5
6
7
8
#include "Key.hpp"
class Lock {
public:
Lock();
virtual ~Lock();
private:
Key m_key;
}

Key.hpp

1
2
3
4
5
6
7
8
#include "Lock.hpp"
class Key {
public:
Key();
virtual ~Key();
private:
m_lock;
}

这个时候出现了两个类相互包含的情况,因为在编译Lock.cpp的时候,我们需要去查看Key的定义,于是去定义Key,在Key中需要Lock的定义,于是又去构造Lock,这样下去,就出现了一个无限循环包含的情况,有些编译器会直接编译错误,而其他编译器会无限包含下去。
这该怎么办呢?有办法,C++为我们提供了前置声明。前置声明是什么?就这个例子来讲,我们要造一把锁,光有锁不行啊,我们还得有能打开这把锁的钥匙。但是锁还没有造好,总不能先打造钥匙吧,钥匙的形状我定了,改天在造。先把锁造好,造锁的时候我要先给要是留一个位置,等锁造好了,我再决定造什么样的钥匙。前置声明就是我在声明一个类(Lock)的时候,用到了另外一个类(Key)的定义,但是Key还没定义呢,而且我先不需要Key的定义,只需要直到Key是一个类就好了。那好,我就先声明类Key,告诉编译器Key是一个类(不需要包含Key.hpp)

1
class Key;

然后在Lock中用到Key的时候,都用指针或者引用代替(因为指针是固定大小的,但Key的大小只有知道了Key的定义才能确定)。然后上面的代码就可以改造为如下:

Lock.hpp

1
2
3
4
5
6
7
8
class Key;
class Lock{
private:
Key *mp_key;
public:
Lock();
virtual ~Lock();
};

Key.hpp

1
2
3
4
5
6
7
8
class Lock;
class Key {
private:
Lock* mp_lock;
public:
Key();
virtual ~Key();
}

前置声明注意的问题

必须使用前置声明的指针或引用形式

在需要使用前置声明的文件中,不可以使用类的定义式,因为类的定义式只有在类定义之后才能获取,这个时候类还没有定义,所以会出错。另一个类在构造的时候编译器是需要计算该对象所占的字节的大小给其分配内存的,但是如果使用类的定义式,这个时候类还未定义,所以无法计算其大小,所以只能使用指针或者引用的形式进行引用。

避免在Lock中使用Key的方法

避免在Key定义之前调用Key的析构函数

Comment and share

条款27:尽量少做转型动作(Effective C++)

C++规则设计的目标之一是,保证类型错误决不可能发生。理论上如果你的程序很干净的通过编译,就表示它并不企图在任何对象身上执行任何不安全,无意义,愚蠢荒谬的操作。这是一个及其具有价值的保证,不要轻易放弃它。
但是在很多种情况下,我们不得不进行转型操作,转型操作破坏了类型系统。这可能会导致任何可能种类的麻烦,有些容易辨识,但是有些可能会很隐晦。所以在需要进行转型操作的时候一定要慎重,尽量通过设计避免不必要的转型操作。

类型转换的形式

首先我们回顾一下类型转换的语法,因为通常有三种不同的形式,可写出相同的类型转换动作。

  • C风格类型转换: (T)expression //将expression转换为类型T
  • 函数式风格类型转换: T(expression) //同上
    上面的两种形式并无差别,纯粹只是把小括号摆放的位置不同而已,我们称上述两种转为为”旧式转型”(old style cast)。

C++还提供四中新式转型(new style):

  • const_cast< T >(expression)
  • dynamic_cast< T >(expression)
  • reinterpret_cast< T >(expression)
  • static_cast< T >(expression)

  • const_cast通常被用来将对象的常量性移除(cast away the constness)。它也是唯一有此能力的C++-style转型操作符。

  • dynamic_cast主要用来执行类型向下转型(safe downcasting),也就是用来决定某对象是否归属继承体系中的某个类型。它是唯一无法由旧式语法执行的动作,也是唯一一个可能耗费重大运行成本的转型动作
  • reinterpret_cast 意图执行低级转型,实际动作及结果可能取决于编译器。这也就表示它不可移植,例如将一个pointer to int 转型为int。这一类型转换在低级代码以外很少见。
  • static_cast 用来强迫隐式转换(implicit conversion),例如将non-const对象转换为const对象,或者将int转换为double等等。也可以用来执行上述多种转换的反向转换,例如将void指针转换为type指针,将pointer to derive 转化为point to bas。但是无法将const转换为non-const。

dynamic_cast与static_cast详解

static_cast是用来强迫隐式类型转换,它可以用于1.基本数据类型以及指针之间的转换;2.类层次中基类与子类成员函数指针的转换;3.类层次结构中基类与子类指针或者引用之间的转换。
dynamic_cast可以用于1.继承关系中类指针或者引用之间的转换;2.包含虚函数之间对象指针的转换3.以及保证转换的安全性。

static_cast

用于基本数据类型转换和指针之间的转换

1
2
3
4
5
6
7
8
char a;
int b = static_cast<int>(a);
char c = static_cast<char>(b);
char *pa = NULL;
int *pb = (int*)pa;
pb = static_cast<int*>(pa); //编译错误static_cast只能用于void指针和type指针之间的转换
void *pv = static_cast<void*>(pa); //正确
pb = static_cast<int*>(pv);

类层次中基类与子类成员函数指针的转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class base {
public:
base(int t_data) : m_data(t_data) {}
void printData() { std::cout << m_data << std::endl; }
private:
int m_data;
};
class child : public base {
public:
child(int t_data) : base(t_data) {}
void printData() { std::cout << "this is in the child" << std::endl; }
};
typedef void (base::*basefun)();
int main() {
base a(10);
basefun func = &base::printData;
func = static_cast<basefun>(&child::printData);
(a.*func)(); //this is in the child
}

类层次结构中基类与子类指针或者引用之间的转换

上行转换:子类指针或引用转换为基类的指针或引用 —安全
下行转换:基类的指针或者引用转换为子类的指针或引用 —危险(避免这样做)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class A
{
};
class B:public A
{
};
class C:public A
{
};
class D
{
};
A objA;
B objB;
A* pObjA = new A();
B* pObjB = new B();
C* pObjC = new C();
D* pObjD = new D();
objA = static_cast<A&>(objB); //转换为基类引用
objA = static_cast<A>(objB);
objB = static_cast<B>(objA); //error 不能进行转换
pObjA = pObjB; //right 基类指针指向子类对象
//objB = objA; //error 子类指针指向基类对象
pObjA = static_cast<A*>(pObjB); //right 基类指针指向子类
pObjB = static_cast<B*>(pObjA); //强制转换 OK 基类到子类
//pObjC = static_cast<C*>(pObjB); //error 继承于统一类的派生指针之间转换
//pObjD = static_cast<D*>(pObjC); //error 两个无关联之间转换

dynamic_cast

继承关系的类指针对象或者引用之间的转换

若积累中没有虚函数,使用dynamic_cast可以将子类的指针或引用转换为基类的指针或引用,与static_cast用法相同,不同的是,这个时候使用dynamic_cast将基类指针转换为子类指针的时候会出现编译错误(static_cast不会,但是很危险)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class A
{
};
class B:public A
{
};
class C:public A
{
};
class D
{
};
A objA;
B objB;
A* pObjA = new A();
B* pObjB = new B();
C* pObjC = new C();
D* pObjD = new D();
//objA = dynamic_cast<A>(objB); //error 非引用
objA = dynamic_cast<A&>(objB);
//objB = dynamic_cast<B&>(objA); //error A 不是多态类型不能转换 若有虚函数则可以进行转换
pObjA = dynamic_cast<A*>(pObjB);
//pObjB = dynamic_cast<B*>(pObjA); //error A 继承关系 不是多态类型不能转换
//pObjB = dynamic_cast<B*>(pObjC); //error C 兄弟关系 不是多态类型不能转换
//pObjB = dynamic_cast<B*>(pObjD); //error D 没有关系 不是多态类型不能转换

包含有虚函数之间的对象指针的转换

使用dynamic_cast将基类指针转换为子类指针的时候并不是永远有效:只有基类指针本身指向的就是一个派生类对象的时候有效。其他时候结果为NULL;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A
{
Public:
Virtual ~A(){}
};
class B:public A
{
};
class C:public A
{
};
class D
{
Public:
Virtual ~D(){}
};
pObjB = dynamic_cast<B*>(pObjA); // worning 继承关系 父类具有虚函数 多态
pObjB = dynamic_cast<B*>(pObjD); //worning 没有关系 D是多态类型可以转换
//以上结果:pObjB == NULL 此处会发生一个运行时错误

dynamic_cast转换的安全性

当涉及到基类和派生类对象之间的转换的时候,总使用dynamic_cast会避免很多错误,它是安全的,但是它会给程序运行带来巨大的开销。
当子类指针转换为基类指针的时候,两种转型都OK,dynamic_cast开销较大。
当基类指针转换为派生类指针的时候,若基类中没有虚函数,static_cast不会报错,但是做法很危险,dynamic_cast编译不通过。当含有虚函数的时候,若基类指针没有指向派生类,这个时候会返回NULL,所以也是安全的。

虚函数对于dynamic_cast转换的作用

为什么dynamic_cast转换类指针的时候需要虚函数呢?
dynamic_cast转换是在运行时进行转换,运行时转换就需要知道类对象的信息(继承关系等)。
在运行时或者这个信息的是虚函数表指针,通过这个指针可以获取到该类对象的所有的虚函数,包括父类的。因为派生类会继承基类的虚函数表,所以通过这个虚函数表,我们就可以知道类对象的父类,在转换的时候就可以用来判断对象有无继承关系。
所以虚函数对于正确的基类指针转换为子类指针是非常重要的。

effective的三点建议

  • 如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast。如果有个设计需要转型动作,试着发展无需转型的替代设计。
  • 如果转型是必要的,试着将它隐藏与某个函数的背后。客户随后可以调用该函数,而不需要将转型放到他们自己的代码中。
  • 宁可使用C++-style转型,不要使用旧式转型。前者很容易辨识出来,而且也比较有着分门别类的职掌

Comment and share

通过/proc/meminfo实时获取系统内存使用情况(Linux)

linux内核提供了一种通过/proc文件系统来在运行时访问内核内部数据结构,改变内核设置的机制,各种硬件平台上的linux系统的/proc文件系统的基本概念都是相同的。
/proc文件系统是一种内核和内核模块用来向进程发送信息的机制。这个伪文件系统可以和内核内部的数据结构进行交互,获取实时的进程信息。注意,/proc文件系统是存储与内存而不是硬盘,/proc虚拟文件系统实质是以文件系统的形式访问内核数据的接口

/proc/meminfo

linux系统中/proc/meminfo这个文件用来记录了系统内存使用的详细情况。其中top,free命令中的数据是通过这个文件中的信息计算并按照特定的格式进行显示。
/proc/meminfo内容详解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
$cat /proc/meminfo
MemTotal: 8052444 kB
MemFree: 2754588 kB
MemAvailable: 3934252 kB
Buffers: 137128 kB
Cached: 1948128 kB
SwapCached: 0 kB
Active: 3650920 kB
Inactive: 1343420 kB
Active(anon): 2913304 kB
Inactive(anon): 727808 kB
Active(file): 737616 kB
Inactive(file): 615612 kB
Unevictable: 196 kB
Mlocked: 196 kB
SwapTotal: 8265724 kB
SwapFree: 8265724 kB
Dirty: 104 kB
Writeback: 0 kB
AnonPages: 2909332 kB
Mapped: 815524 kB
Shmem: 732032 kB
Slab: 153096 kB
SReclaimable: 99684 kB
SUnreclaim: 53412 kB
KernelStack: 14288 kB
PageTables: 62192 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 12291944 kB
Committed_AS: 11398920 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 0 kB
VmallocChunk: 0 kB
HardwareCorrupted: 0 kB
AnonHugePages: 1380352 kB
CmaTotal: 0 kB
CmaFree: 0 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 201472 kB
DirectMap2M: 5967872 kB
DirectMap1G: 3145728 kB

详解:

  • MemTotal: 所有内存(RAM)大小,减去一些预留空间和内核的大小。
  • MemFree: 完全没有用到的物理内存,lowFree+highFree
  • MemAvailable: 在不使用交换空间的情况下,启动一个新的应用最大可用内存的大小,计算方式:MemFree+Active(file)+Inactive(file)-(watermark+min(watermark,Active(file)+Inactive(file)/2))
  • Buffers: 块设备所占用的缓存页,包括:直接读写块设备以及文件系统元数据(metadata),比如superblock使用的缓存页。
  • Cached: 表示普通文件数据所占用的缓存页。
  • SwapCached: swap cache中包含的是被确定要swapping换页,但是尚未写入物理交换区的匿名内存页。那些匿名内存页,比如用户进程malloc申请的内存页是没有关联任何文件的,如果发生swapping换页,这类内存会被写入到交换区。
  • Active: active包含active anon和active file
  • Inactive: inactive包含inactive anon和inactive file
  • Active(anon): anonymous pages(匿名页),用户进程的内存页分为两种:与文件关联的内存页(比如程序文件,数据文件对应的内存页)和与内存无关的内存页(比如进程的堆栈,用malloc申请的内存),前者称为file pages或mapped pages,后者称为匿名页。
  • Inactive(anon): 见上
  • Active(file): 见上
  • Inactive(file): 见上
  • SwapTotal: 可用的swap空间的总的大小(swap分区在物理内存不够的情况下,把硬盘空间的一部分释放出来,以供当前程序使用)
  • SwapFree: 当前剩余的swap的大小
  • Dirty: 需要写入磁盘的内存去的大小
  • Writeback: 正在被写回的内存区的大小
  • AnonPages: 未映射页的内存的大小
  • Mapped: 设备和文件等映射的大小
  • Slab: 内核数据结构slab的大小
  • SReclaimable: 可回收的slab的大小
  • SUnreclaim: 不可回收的slab的大小
  • PageTables: 管理内存页页面的大小
  • NFS_Unstable: 不稳定页表的大小
  • VmallocTotal: Vmalloc内存区的大小
  • VmallocUsed: 已用Vmalloc内存区的大小
  • VmallocChunk: vmalloc区可用的连续最大快的大小

通过/proc/meminfo实时获取系统内存使用情况

http://man.linuxde.net/free
目前我们希望实时获取系统中内存的使用情况,实际可以挪用的内存数为free+cache+buffer,实际使用的内存数为used-cache-buffer(total-free-cache-buffer),

Comment and share

通过/proc/stat计算linux系统即时CPU使用率

/proc/stat

/proc文件系统是一个伪文件系统,它存在于内存中,不占用外存空间。它以文件系统的方式为内核与进程提供通信接口。用户和应用程序可以通过/proc得到系统的信息,并可以改变内核的某些参数。由于系统的信息,如进程是动态改变的,所以用户或应用程序读取/proc目录中的文件的时,/proc文件系统是动态从系统内核读出所需信息并提交的。
在linux或Ubuntu系统中,/proc/stat文件记录了所有CPU活动的信息,该文件中的所有值都是从系统启动开始累计到当前的值。我们可以通过一个较小的时间段的CPU利用率值来估算某一个时刻的CPU利用率。首先我们先来查看一下/proc/stat中的内容:

1
2
3
4
5
6
$ cat /proc/stat
cpu 15543 334 4963 142337 3413 0 180 0 0 0
cpu0 3877 142 1137 35988 615 0 28 0 0 0
cpu1 3866 23 1068 35982 608 0 74 0 0 0
cpu2 4104 159 1333 35395 735 0 59 0 0 0
cpu3 3695 9 1424 34970 1454 0 18 0 0 0

上面的信息中前八个是比较重要的。

  • user(15543): 从系统启动到当前时刻,处于用户态的时间(用户空间的CPU时间),不包括nice为负值的进程。
  • nice(334): 从系统启动到当前时刻,nice值为负的进程所占的CPU时间。
  • system(4963): 从系统启动到当前时刻,处于内核状态的CPU时间。
  • idle(142337): 从系统启动到当前时刻,除了I/O等待时间以外的其他等待时间
  • iowait(3413): ~,IO等待时间
  • irq(0): ~, 硬中断时间
  • softrq(180): ~,软中断时间。
  • steal: ~,在虚拟环境中运行的时间。

总的CPU时间为:total = user+nice+system+idle+iowait+irq+softrq

计算当前时刻的CPU使用率

我们使用距离当前时刻较小的间隔的时间段的CPU使用率近似当前时刻的CPU使用率。

1
2
CPU Usage=1 - (△idle+△iowait) / △total_time
=1 - (idle2+iowait2 - idle1 - iowait1) / (total_time2 - total_time1)

Comment and share

linux进程nice值及其与优先级的关系

在linux多任务环境的系统中,系统是根据进程的优先级(priority)给进程进行分配资源。优先级越高的进程越有优先执行的权利。配置进程优先权碎玉linux很有用,可以大大改善系统的性能。还可以把指定的进程运行在指定的cpu上。

nice与priority

在linux中使用ps 的-l参数可以对进程的优先权信息进行查看:

1
2
3
4
5
6
$ ps -l
#打印信息:
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 5242 5232 0 80 0 - 7461 wait pts/1 00:00:00 bash
4 T 1000 5881 5242 0 80 0 - 12247 signal pts/1 00:00:01 top
4 R 1000 6386 5242 0 80 0 - 8996 - pts/1 00:00:00 ps

从上面可以看出,上表中打印出一些有用的信息:

  • UID: 进程的user id
  • PID: 进程ID
  • PPID: 当前进程的父进程的ID
  • PRI: 优先级(pri越小优先级越高)
  • NI: nice值,当前进程的nice值
    前三个值比较容易理解,pri和NI的含义,pri比较好理解,即进程的优先级,pri越小,优先级越高,那nice值呢?nice表示进程可被执行的优先级的修正数值。如前面说的,pri越小越优先被执行,那么加入nice之后pri(new)=pri(old)+nice。这样,当nice为负值的时候,该程序的pri变小,优先级越高。

    注意:进程的nice值不是进程的优先级,但是会影响进程优先级的变化。

nice相关linux命令nice与renice

1.nice的作用是启动时设置nice的值

1
ice -n -5 ./MatrixJudge &

-n参数用于指定nice值
2.renice的作用是修改已经存在的进程的nice值

1
renice -5 -p 10000

上述的意思是将PID为10000的nice值置为-5,-p参数用于指定PID。

Comment and share

tornado表单和模板

在第一章中,我们学习了使用tornado创建一个web应用的基础知识。包括处理函数,HTTP方法以及tornado的框架的总体结构。在这一章中,我们学习tornado的更加强大的功能—-表单和模板。
和大多数的web框架一样,tornado的一个重要的目标就是帮助你更快地编写程序,尽可能整洁地复用更多的代码。尽管tornado灵活,可以使用几乎所有python支持的模板语言,tornado自身也提供了一个轻量级,快速并且灵活的模板语言,在tornado.template模块中。

简单示例 Poem Maker Pro

poemmaker.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import os.path
import tornado.httpserver
import tornado.options
import tornado.web
import tornado.ioloop
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")
class PoemPageHandler(tornado.web.RequestHandler):
def post(self):
noun1 = self.get_argument("noun1")
noun2 = self.get_argument("noun2")
verb = self.get_argument("verb")
noun3 = self.get_argument("noun3")
self.render("poem.html", roads=noun1, wood=noun2, made = verb, difference=noun3)
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(handlers=[(r"/", IndexHandler),
(r"/poem", PoemPageHandler)],
template_path = os.path.join(os.path.dirname(__file__), "templates"),
static_path = os.path.join(os.path.dirname(__file__), "static"),
debug=True)
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()

除了上述poemmaker.py,还需要在文件夹templates下添加以下两个文件。
/templates/index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<head><title>Poem Maker Pro</title></head>
<body>
<h1>Enter terms below.</h1>
<form method="post" action="/poem">
<p>Plural noun <br/> <input type="text" name="noun1" /></p>
<p>Singular noun<br/> <input type="text" name="noun2" /></p>
<p>Verb (haha)<br /> <input type="text" name="verb" /></p>
<p>Noun<br/> <input type="text" name="noun3" /> </p>
<input type="submit"/>
</form>
</body>
</html>

/templates/poem.html

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head><title>this is the poem</title></head>
<body>
<h1>Your poem</h1>
<p>Two {{roads}} diverged in a {{wood}}, and I—<br/>
I took the one less tracelled by, <br/>
and that has {{made}} all the {{difference}}.</p>
</body>
</html>

运行python文件:

1
python poemmaker.py

此时访问8000端口会出现首页上包括多个表单等待用户输入。这个表单包括多个文本域(命名为noun1, noun2等),其中内容将在用户点击“submit”按钮时以post请求的方式发送到/poem。
当点击submit按钮之后,tornado应用跳转到poem.html,插入你在表单中填写的值。结果是显示的一首诗。

下面我们逐条讲述当前示例中的每一个涉及到的知识点:

2.1.1渲染模板

从结构上讲,poemmaker和第一章的例子很相似。我们定义了几个RequestHandler子类并把它们传递给tornado.web.Application对象。那么有什么不一样的地方呢?首先我们向Application对象的init方法传递了一个template参数。

1
template_path = os.path.join(os.path.dirname(__file__), "templates")

template_path参数告诉tornado在哪里寻找模板文件。我们将在第三章中讲解其确切的用法和性质。而它的基本要点是:模板是一个允许你嵌入python代码片段的html文件。上面的代码告诉python在你的tornado应用文件相同目录下的templates文件夹下寻找模板文件。
一旦我们告诉tornado在哪里寻找模板,我们就可以使用RequestHandlser类的render方法来告诉tornado读入模板文件,插入其中的模板代码,并将结果返回给浏览器。比如,在IndexHandler中,我们可以发现语句:

1
self.render("index.html")

这段代码告诉tornado载templates文件夹下找到一个名字为index.html的文件,读取其中的内容,并发送给浏览器。

2.12 填充

实际上index.html完全不能称之为模板,因为它所包含的完全是已经编写好的html标记。这可以是模板一个不错的使用方式。但是在通常情况下我们更希望html输出可以结合我们的程序传入给模板的值。模板poem.html使用PoemPageMaker渲染,它是一个模板。
我们可以看到,在poem.html中,它可以看到模板中有一些被双大括号括起来的字符串,就像这样:

1
2
3
<p>Two {{roads}} diverged in a {{wood}}, and I—<br/>
I took the one less tracelled by, <br/>
and that has {{made}} all the {{difference}}.</p>

在双大括号中的单词是占位符,当我们渲染模板时希望以实际值代替。我们可以使用render函数中传递关键字参数的方法 指定什么值将被填充到html文件中的对应位置,其中关键字对应模板文件中占位符的名字。下面是在poemPageMaker中相应的代码部分:

1
2
3
4
5
noun1 = self.get_argument("noun1")
noun2 = self.get_argument("noun2")
verb = self.get_argument("verb")
noun3 = self.get_argument("noun3")
self.render("poem.html", roads=noun1, wood=noun2, made = verb, difference=noun3)

在这里,我们告诉模板使用变量noun1作为模板中roads的值,以此类推。

2.13 设置静态路径

你可以通过向Application类的构造函数传递一个名为static_path的参数来告诉tornado从文件系统一个特定的位置提供静态文件。
在上述代码中如下:

1
static_path = os.path.join(os.path.dirname(__file__), "static"))

在这里,我们设置了一个当前应用目录下名为static的子目录作为static_path的参数。应用将读取static中的文件作为静态文件

2.1.4使用static_url生成url

tornado模板提供了一个叫做static_url的函数生成static目录下文件的url.让我们来看看在index.html中static_url的调用代码的示例代码:

1
<link rel="stylesheet" href="{{static_url("style.css")}}">

这个对static_url的调用生成了url的值,并渲染出类似下面的代码:

1
<link rel="stylesheet" href="/static/style.css?v=ab12>

那么为什么使用static_url而不是在你的模板中硬编码呢?原因有以下两点:
第一,static_url函数创建了一个基于文件内容的hash值,并将其添加到url末尾(查询字符串的参数是v).这个hash值确保浏览器总是加载一个文件的最新版本而不是以前的缓存版本。无论是在开发阶段还是部署到生产环境,都非常有用,因为用户不必为了可到你的静态内容去清除浏览器的缓存了。
第二,你可以改变你的url结构,而不用改变模板中的代码。例如,你可以配置tornado响应来自路径/s/filename的请求时提供静态内容,而不是默认的/static路径。如果你使用static_url而不是硬编码,这个时候你不需要改变代码中的值。

debug参数

在这个例子中,你可能注意到了debug=True的使用。它调用了一个便利的测试模块:tornado.autoreload模块,此时,一旦主要的python文件被修改,tornado会尝试重启服务器,而且载模板改变的时候会自动刷新。对于快速改变和实时更新这非常棒,但不要在生产上使用它,因为他将防止tornado缓存模板。

2.2模板语法

既然我们已经看到了一个模板在实际应用中的例子,哪买让我们深入了解它们是如何工作的。tornado模板是被python表达式和控制语句标记的简单文本文件。tornado的语法非常直接。在2.1节中,我们展示了如何在一个web应用中使用render方法传送html给浏览器。你可以在tornado应用之外使用python解释器导入模块尝试模板系统,此时结果会被直接输出出来。

1
2
3
4
>>> from tornado.template import Template
>>> content = Template("<html><body><h1>{{header}}</h1></body></html>")
>>> print content.generate(header="Welcome!")
<html><body><h1>Welcome!</h1></body></html>

2.21填充表达式

上面的演示我们也可以看出,向html中填充python变量的值,我们可以使用双大括号;其实我们可以在双大括号中插入python表达式,它会自动计算出其实际值:

1
2
3
4
5
6
7
>>> from tornado.template import Template
>>> print Template("{{ 1+1 }}").generate()
2
>>> print Template("{{ 'scrambled eggs'[-4:] }}").generate()
eggs
>>> print Template("{{ ', '.join([str(x*x) for x in range(10)]) }}").generate()
0, 1, 4, 9, 16, 25, 36, 49, 64, 81

2.22 控制流语句

同样也可以在tornado模板中使用python条件和循环语句。控制与句以{ %和% }包围,并以类似下面形式使用:

1
2
3
{% if page is None %}
{% if len(entrise) == 3 %}

控制语句的大部分就像对应的python语句一样工作,支持if,for,wihle,try。在这些情况下,语句块以{ %开始,以 % }结束

模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<head><title>{{title}}</title></head>
<body>
<h1>{{header}}</h1>
<ul>
{%for book in books%}
<li>{{book}}</li>
{% end %}
</ul>
</body>
</html>

处理函数:

1
2
3
4
class BookHandler(tornado.web.RequestHandler):
def get(self):
self.render("books.html", title="Home Page", header="Books that are great",\
books=["python", "c++", "pascal"])

这个时候将渲染出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head><title>{{title}}</title></head>
<body>
<h1>{{header}}</h1>
<ul>
<li>python</li>
<li>C++</li>
<li>pascal</li>
{% end %}
</ul>
</body>
</html>

不像其他的python模板渲染系统,tornado模板语言的一个最好的东西是在if和for语句块中可以使用的表达式没有限制.因此,你可以在你的模板中执行所有的python代码。
同样,你也可以在你的控制语句块中间使用{ % set foo = “bar” % }来设置变量的值。你还有很多可以在控制语句块中做的事情,但是,在大多数情况下,你最好使用UI模块来做更复杂的划分。我们稍后会更详细的看到这一点。

2.2.3在模板中使用函数

tornado在所有模板中默认提供了一些遍历的函数。他们包括:

1
2
3
4
5
6
7
8
#替换字符串s中的&,<,>为他们对应的html字符
escape(s)
# 使用urllib.quote_plus替换字符串s中的字符为URL编码形式
url_escape(s)
#将val编码成json格式,底层使用json.dumps()
json_encode(val)
#过滤字符串s,把连续的多个空白字符替换成一个空格
squeeze(s)

在模板中使用一个你自己编写的函数也很简单:只需要将函数名作为模板的参数传递即可,就像其他的变量。

1
2
3
4
5
6
7
8
9
>>> from tornado.template import Template
>>> def disemvowel(s):
... return ''.join([x for x in s if x not in 'aeiou'])
...
>>> disemvowel("george")
'grg'
>>> print Template("my name is {{d('mortimer')}}").generate(d=disemvowel)
my name is mrtmr

Comment and share

tornado入门

tornado是使用python编写的一个强大的,可扩展的web服务器。它在高并发的网络请求中表现的足够稳定,但是却在创建和编写的过程中有着足够的轻量级,并能够被用在大量的应用和工具中。
不同于那些最多只能达到10000个并发连接的传统web服务器,tornado在设计之初就考虑到了性能因素,旨在解决C10K问题,这样的设计使得其成为一个非常高性能的框架。此外,它还拥有处理安全性,用户验证,社交网络以及与外部服务进行异步交互的工具。

Hello tornado

tornado是一个编写对HTTP请求响应的框架。作为程序员,你的工作是编写响应特定条件HTTP请求的响应的handler。

hello.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
define("port", default=8000, help = "run on the give port", type = int)
class indexHandler(tornado.web.RequestHandler):
def get(self):
greeting = self.get_argument("greeting", "hello")
self.write(greeting + ", friend user!")
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(handlers=[(r"/", indexHandler)])
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()

编写一个tornado应用中最多的工作是定义继承自tornado.RequestHandler的类。在这个例子中,我们创建了一个简单的应用,在指定的端口监听请求,并在根目录(“/“)下响应请求。
执行命令:

1
python hello.py --port=9999

使用命令对其访问

1
2
3
4
5
curl http://localhost:9999
hello, friend user!
curl http://localhost:9999/?greeting=david
david, friend user!

现在tornado程序就已经在localhost:9999处进行监听。我们可以对其进行根目录使用get方法进行那个访问.

程序分析

下面我们开始逐条语句进行分析:
开始的import语句导入了程序所必须的tornado的相关模块。虽然tornado还有其他模块,但是我们在这个程序中只需要这四个模块

tornado中options模块分析

1
2
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)

tornado包含了一个有用的模块tornado.options来从命令行读取设置。我们在这里使用这个模块指定我们监听http请求的端口。它的工作流程是:
如果一个与define语句中同名的设置在命令行中给出,那么它将成为全局options的一个属性。如果用户程序在运行的时候使用了–help选项,程序将打印出你所定义的选项以及你在define函数的help参数中指定的文本。如果程序没有为这个选项指定值,那么则使用default进行替代。tornado使用type来验证参数的类型,当类型不合适的时候跑出一个异常。因此,我们允许一个整数port作为options.port来访问程序,如果用户没有指定port的值,则默认为9999端口。

请求处理函数类及方法

1
2
3
4
class indexHandler(tornado.web.RequestHandler):
def get(self):
greeting = self.get_argument("greeting", "hello")
self.write(greeting + ", friend user!")

这是tornado请求处理函数类,它继承自tornado.web.RequestHandler,当处理一个请求的时候,tornado将这个类实例化,并调用http请求方法所对应的方法。在这个例子中,我们只定义了get方法,也就是说这个类对http的GET请求作出响应。我们稍后将看到实现不止一个http方法的处理函数。

获取查询参数

1
self.get_argument("greeting", "hello")

tornado的request类有很多有用的内建函数,包括get_argument()用来用一个查询字符串中获取参数的值。上述的意思是从查询字符串中获取查询参数greeting的值,若查询参数中没有greeting参数,则会其值为”hello”,即为其默认值。

RequestHandler的write函数

1
self.write(greeting + ", firendly user")

tornado另一个有用的方法是write,它以一个字符串作为参数,并将其写入到http响应中去。在这里,我们使用请求中greeting的参数提供的值插入到greeting中,并写回到相应中去。

创建Application实例

1
2
3
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(handlers=[(r"/", indexHandler)])

这里是真正使得tornado运转起来的语句。首先我们使用tornado的options解析命令行。然后我们创建了一个tornado的Application实例。传递给Application的init方法的最重要的参数就是handlers。它告诉tornado用那个类来响应特定的请求。

端口监听

1
2
3
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()

从这离开始的代码会被反复使用,一旦Application对象被创建,我们可以将其传递给httpServer的对象,然后使用我们在命令行指定的端口进行监听。最后,在程序准备好接收HTTP请求后,我们创建一个tornado的IOLoop的实例进行启动。

简单字符串服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import textwrap
import tornado.web
import tornado.httpserver
import tornado.ioloop
import tornado.options
from tornado.options import define,options
define("port", default=8000, help="run on the given port", type=int)
class ReverseHandler(tornado.web.RequestHandler):
def get(self, input1, input2):
self.write(input1)
self.write(input2)
class WrapHandler(tornado.web.RequestHandler):
def post(self):
text =self.get_argument("text")
width = self.get_argument("width", 40)
self.write(textwrap.fill(text, int(width)))
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(handlers=[(r"/reverse/([0-9]+)(\w+)", ReverseHandler),\
(r"/wrap", WrapHandler)])
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()

上述是一个简单的字符串操作的web服务,有两个作用:第一,到/reverse/string的GET请求奖会返回url路径中指定字符串的反转形式。

1
2
$curl http://localhost:8000/reverse/123456
654321

作用二是,到/wrap的POST请求从参数text中取得指定文本,并返回按照参数width指定宽度装饰的文本。下面的请求指定一个没有宽度的字符串,所以它的输出宽度被指定为程序中的get_argument的默认值40

1
2
3
$http://localhost:8000/wrap -d text=Lorem+ipsum+dolor+sit+amet,+consectetuer+adipiscing+elit.
Lorem ipsum dolor sit amet, consectetuer
adipiscing elit.

RequestHandler的更多知识

HTTP方法

到目前为止我们只用了http中的get,post方法,但tornado支持任何合法的HTTP请求(GET,POST,HEAD,DELETE,OPTIONS)。你可以非常容易的定义上述任何一种方法的行为,只需要在RequestHandler类中使用同名方法。

HTTP状态码

从上面的代码可以看出,你可以使用RequestHandler类的set_status()方法显式的去设置http的状态麻,但是你需要记住在某些情况下,tornado会自动的设置状态码。下面是一种常见的纲要。

  • 404 Not Found
    当http请求的路径无法匹配到任何RequestHandler类对应的模式的时候tornado会返回404相应码。
  • 400 Bad Request
    如果你调用了一个没有默认值的get_argument()方法,并且没有发现给定名称的参数,tornado将自动返回一个400响应码
  • 405 Method Not Allowed
    如果传入的请求使用了RequestHandler中没有定义的http方法,tornado将自动返回一个405响应码。
  • 500 Internal Server Error
    当程序遇到任何不能让其退出的错误的时候,tornado自动返回响应码500.代码中任何没有捕获的异常也会返回500.
  • 200 OK
    如果相应成功,并没有其他返回码被设置,tornado将默认返回一个200.

Comment and share

迭代器和简单的生成器

摘自: https://www.ibm.com/developerworks/cn/linux/sdk/python/charm-20/

python2.2引进了一种带有新型关键字的新型构造。这个构造是生成器;关键字是yield.生成器使几个新型,强大和富有表现力的编程习惯成为可能,但初看,要理解生成器,还是有一点难度。本文由浅入深的介绍了生成器,同时还介绍了迭代器的相关问题。

由于迭代器比较容易理解,让我们先看它。基本上迭代器是含有.next方法的对象。这样定义不十分正确但非常接近。事实上,当迭代器应用新的iter()内置函数的时候,大多数迭代器的上下文希望返回迭代器,需要使iter()方法返回self.本文的示例将会说明清楚这一点。如果迭代有一个逻辑的终止,则迭代器的.next()方法可能决定抛出StopIteration异常。
生成器要稍微复杂化和一般化一点。但生成器典型的用途是用来定义迭代器;所以不值得总是为一些细微之处而担心,生成器是这样一个函数它记住上一次返回时在函数体中的位置,对于生成器函数的第二次调用跳转到该函数的中间,而上次调用的所有的局部变量都被记住

随机遍历

让我们考虑一个简单的问题,可以使用很多方法解决它。假设我们想要一串正的随机数字流,这个数字流的要求是每一个数字不允许小于0.1,且相邻两个数字之间的大小相差绝对值不小于0.4

版本一

版本一是比较传统的做法,我们直接写一个随机产生一串数字流的函数,并将他保存在一个list中返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def generate_random():
last, rand = 1, random.random()
num = []
while rand > 0.1:
if abs(last - rand) >= 0.4:
last = rand
num.append(rand)
else:
print "invalid"
rand = random.random()
num.append(rand)
return num
for item in generate_random():
print item

从上面可以产出,这种方法有很大的局限性。首先这个示例中不可能产生庞大的数字列表,通过对数字的大小限制进行终结随机数的产生,我们可以通过限制进行预测此数列的大小。另一方面,在内存和性能方面使这种方法变得不切实际,以及没有必要。同样是这个问题,使得python较早的版本中添加了xrange()和xreadlines()。重要的是,许多流取决于外部事件,并且当每个元素可用时,才处理这些流。
在python2.1和较早的版本中,我们的诀窍是使用静态函数局部变量来记住函数上一次调用的事情。显而易见,全局变量可以用来做同样的工作,但是它会带来大家熟知的问题:命名空间污染问题

###版本二 使用静态成员解决上述问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def generate_random_static(last = [1]):
rand = random.random()
if rand < 0.1:
return
while abs(rand - last[0]) < 0.4:
print "invalid"
rand = random.random()
last[0] = rand
return rand
num = generate_random_static()
while num is not None:
print num
num = generate_random_static()

这个函数是一个很好的存储器,它只需要记住上一次的的值,返回一个单一的数字。而不需要存储一整个列表。并且与此类似的函数可以返回却绝育外部事件的连续的值。不方便的是使用这个函数不够方便,且想当不灵活

版本三 定义迭代器类

实质上python2.2的序列都是迭代器,python常见的习惯用法for elem in lst:而实际上是让;lst差生一个迭代器。然后for循环调用这个迭代器的.next()方法,直到遇到StopIteration为止。幸运的是,由于常见的内置类型自动产生它们的迭代器,所以python程序员不需要直到这里发生了什么。实际上,现在字典里有.iterkeys(),.iteritems(),.itervalues()方法产生迭代器。定制类支持直接使用randomwalk_list()以及一次一个元素这种极度节省的randomwalk_static,这是简单易懂的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class generate_random_class(object):
def __init__(self):
self.last = 1
self.rand = random.random()
def __iter__(self):
return self
def next(self):
if self.rand < 0.1:
raise StopIteration
else:
while abs(self.last - self.rand) < 0.4:
print "invalid"
self.rand = random.random()
self.last = self.rand
return self.rand
for item in generate_random_class():
print item

版本四 生成器版本

上述方法也会产生较多的问题,虽然它避免了产生整个list,大量局部变量的情况,但是当迭代器类或者函数取决于多个数据状态的时候,就需要记录多个数据的状态,这种情况下出现错误的记录较大。所以也不是一种较好的解决方法。使用python自带的生成器yield关键字
在 Python 2.2+ 中,不直接 写生成器。 相反,编写一个函数,当调用它时,返回生成器。这可能看起来有点古怪,但“函数工厂”是 Python 的常见特性,并且“生成器工厂”明显是这个概念性扩展。在 Python 2.2+ 中使函数成为生成器工厂是它主体某处的一个或多个 yield 语句。如果 yield 发生, return 一定只发生在没有伴随任何返回值的情况中。 然而,一个较好的选择是,安排函数体以便于完成所有 yield 之后,执行就“跳转到结束”。但如果遇到 return ,它导致产生的生成器抛出 StopIteration 异常,而不是进一步生成值。

1
2
3
4
5
6
7
8
9
10
11
def generate_random_yield():
last, rand = 1, random.random()
while rand > 0.1:
if abs(last - rand) >= 0.4:
last = rand
yield rand
rand = random.random()
yield rand
for item in generate_random_yield():
print item

上述做法的好处是逻辑简洁,与正常写法无意,也保证了通用性。

Comment and share

魏传柳(2824759538@qq.com)

author.bio


Tencent


ShenZhen,China