Rao's Blog

  • 首页

  • 标签

  • 分类

  • 归档

  • 搜索

Linux 磁盘相关知识

发表于 2019-10-25 | 更新于 2019-11-15 | 分类于 Linux

磁盘类型

按照材质分类:

  • HDD(Hard Disk Driver):传统硬盘,即机械硬盘
  • SSD(Solid State Disk):固态硬盘,是一种主要以闪存作为永久性存储器的计算机存储设备。

按照硬盘接口分类:硬盘接口是指硬盘与主板之间的连接方式

  • SATA:串形高技术配置接口
  • SAS:串形小型计算机系统接口
  • SAS 接口的硬盘比 SATA 接口的硬盘传输速度要快很多,并且性能也高很多

磁盘类型:使用 lsblk 命令进行判断

  • -d ,表示显示设备名称
  • -o ,表示仅显示特定的列
  • ROTA 是 1 的表示可以旋转,磁盘类型是 HDD。
  • ROTA 是 0 的表示不可以旋转,就有可能是 SSD。
1
2
3
4
5
6
$ lsblk -d -o name,rota
NAME ROTA
fd0 1
sda 1
sdb 1
sr0 1

磁盘性能

硬盘性能指标:IOPS (Input / Output Per Second) 即每秒的输入输出量(读写次数),是衡量磁盘性能的主要指标之一。

使用 dd 测试硬盘吞吐量:

1
2
3
4
$ dd if=/dev/zero of=/dev/sda bs=4k count=3000 oflag=direct
记录了3000+0 的读入
记录了3000+0 的写出
12288000字节(12 MB)已复制,0.0791091 秒,155 MB/秒

总结:SSD 的随机 IO 读写能力是 SATA 和 SAS 机械硬盘的一千多倍,顺序读写能力基本和 SSD 保持同样的数量级。

MySQL · 配置参数 · expire_logs_days

发表于 2019-10-25 | 更新于 2019-11-15 | 分类于 MySQL

参数说明

expire_logs_days:控制 binlog 日志的过期时间,默认是 0 表示永不过期,单位是天。

Property Value
Command-Line Format --expire-logs-days=#
System Variable expire_logs_days
Scope Global
Dynamic Yes
Type Integer
Default Value 0
Minimum Value 0
Maximum Value 99

设置

  • 永久生效
1
2
# /etc/my.cnf
expire_logs_days = 7
1
2
3
4
5
6
7
mysql> show variables like "expire_logs%";
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| expire_logs_days | 7 |
+------------------+-------+
1 row in set (0.00 sec)
  • 临时生效
1
2
3
4
5
# 设置保留7天
set global expire_logs_days = 7

# 刷新日志
flush logs;

Warning:以上命令在数据库执行会立即生效,请确定设置数据的保留日期,以免误删!

MySQL · 配置参数 · sync_binlog

发表于 2019-10-25 | 更新于 2019-11-15 | 分类于 MySQL

参数说明

sync_binlog:控制 MySQL 二进制日志(binlog)同步到磁盘的频率。

Property Value
Command-Line Format --sync-binlog=#
System Variable sync_binlog
Scope Global
Dynamic Yes
Type Integer
Default Value (>= 5.7.7) 1
Default Value (<= 5.7.6) 0
Minimum Value 0
Maximum Value 4294967295
  • sync_binlog = 0,每进行事务提交后,MySQL 不做 fsync 之类的磁盘同步指令刷新 binlog_cache 中的信息到磁盘,而让文件系统自行决定什么时候来做同步,或者 cache 满了之后才同步到磁盘。
  • sync_binlog = 1,每进行 1 次事务提交,MySQL 将进行一次 fsync 之类的磁盘同步指令来将 binlog_cache 中的数据强制写入磁盘。
  • sync_binlog = N,每进行 N 次事务提交,MySQL 将进行一次 fsync 之类的磁盘同步指令来将 binlog_cache 中的数据强制写入磁盘。

设置

  • 性能:0 > N > 1
  • 安全:1 > N > 0
  • 建议:区分业务场景进行设置
1
2
# 支付场景:sync_binlog = 1
# 其他场景:sync_binlog = 0

MySQL · 配置参数 · innodb_flush_log_at_trx_commit

发表于 2019-10-25 | 更新于 2019-11-15 | 分类于 MySQL

参数说明

innodb_flush_log_at_trx_commit:控制 ib_logfile 日志(记录 redo log undo log 信息)的刷新方式。

Property Value
Command-Line Format --innodb-flush-log-at-trx-commit=#
System Variable innodb_flush_log_at_trx_commit
Scope Global
Dynamic Yes
Type Enumeration
Default Value 1
Valid Values 0 1 2
  • innodb_flush_log_at_trx_commit = 0,表示每隔 1 秒把 log buffer 刷到 log file 中去,并且同时调用文件系统的 flush 操作将缓存刷新到磁盘上去。也就是说一秒之前的日志都保存在日志缓冲区,也就是内存上,在mysqld 进程崩溃的情况下,可能丢失 1 秒的事务数据。
  • innodb_flush_log_at_trx_commit = 1,表示在每次事务提交后,会把 log buffer 刷到 log file 中去,并且调用文件系统的 flush 操作将缓存刷新到磁盘上去。
  • innodb_flush_log_at_trx_commit = 2,表示在每次事务提交后,会把 log buffer 刷到 log file 中去,但并不会立即刷写到磁盘。只有在数据库所在的主机操作系统损坏或者突然掉电的情况下,可能丢失 1 秒的事务数据。

设置

  • 性能:0 > 2 > 1
  • 安全:1 > 2 >0
  • 推荐:区分业务场景进行设置
1
2
# 支付场景:innodb_flush_log_at_trx_commit = 1
# 其他场景:innodb_flush_log_at_trx_commit = 2

MySQL · 配置参数 · max_connections

发表于 2019-10-24 | 更新于 2019-10-25 | 分类于 MySQL

参数说明

  • max_connections:MySQL 实例最大连接数。
  • Max_used_connections:是指从本次 MySQL 服务启动到现在,同一时刻并行连接数的最大值,不是指当前的连接情况,而是一个比较值。
Property Value
Command-Line Format --max-connections=#
System Variable max_connections
Scope Global
Dynamic Yes
Type Integer
Default Value 151
Minimum Value 1
Maximum Value 100000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> show variables like "max_connections";
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| max_connections | 1000 |
+-----------------+-------+
1 row in set (0.00 sec)

mysql> show status like "max_used%";
+----------------------+-------+
| Variable_name | Value |
+----------------------+-------+
| Max_used_connections | 391 |
+----------------------+-------+
1 row in set (0.00 sec)

设置

推荐:如果设置太高硬件吃不消,太低又不能充分利用硬件,一般要求两者比值超过 10%。

1
Max_used_connections / max_connections * 100%  > 10%

方法:/etc/my.cnf 添加参数 max_connections = 1000,这个值的大小取决于服务器的配置。

其他

如果你看到数据库报 "Too many connections" 错误,是因为当前连接数超过 max_connections 设置的值,常见的原因是应用程序没有正确的关闭数据库连接,应用程序使用连接池有助于解决这一问题。

MySQL · 调优案例 · JOIN

发表于 2019-10-24 | 更新于 2019-11-15 | 分类于 MySQL

JOIN

INNER JOIN(内连接)

LEFT JOIN(左连接)

RIGHT JOIN(右连接)

MySQL · 主键

发表于 2019-10-24 | 更新于 2019-12-10 | 分类于 MySQL

背景

问题:

有一张表,里面有 id 自增主键,当 insert 了 17 条记录之后,删除了第 15 16 17 条记录,再把 MySQL 重启,再 insert 一条记录,这条记录的 id 是 18 还是 15 ?

答案:

MySQL 8.0 之前

  • 如果是 MyISAM 表,数据库重启后,id 值为 18
  • 如果是 InnoDB 表,数据库重启后,id 值为 15

MySQL 8.0 开始

  • 如果是 MyISAM 表,数据库重启后,id 值为 18
  • 如果是 InnoDB 表,数据库重启后,id 值为 18

原理

不同存储引擎自增主键实现机制不同,同一存储引擎不通版本实现机制不同。

MyISAM:自增主键的最大 id 记录在数据文件里,重启后不会消失。

InnoDB

  • MySQL 5.7 及之前版本,自增主键最大 id 保存在内存里,重启数据库或者是对表进行 optimize 操作,都会导致最大 id 丢失,重启后将 max(id)+1 作为这个表当前的自增值。
  • MySQL 8.0 版本,自增主键最大 id 记录在了 redo log 中,重启后依靠 redo log 恢复重启之前的值。

测试

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
48
49
mysql> select * from employees_innodb;
+----+------------+--------------------+-----+---------------------+---------------------+
| id | name | address | age | gmt_create | gmt_modified |
+----+------------+--------------------+-----+---------------------+---------------------+
| 1 | xujinliang | 山东省青岛市 | 32 | 2019-09-24 17:13:00 | 2019-09-24 17:13:00 |
| 2 | maofang | 山东省青岛市 | 32 | 2019-09-24 17:32:00 | 2019-09-24 17:40:49 |
| 3 | raohui | 陕西省西安市 | 29 | 2019-09-24 17:39:28 | 2019-09-24 17:39:28 |
| 4 | yangnan | NULL | 22 | 2019-09-24 17:39:52 | 2019-09-24 17:39:52 |
| 5 | yuzhiqiang | 山东省青岛市 | 29 | 2019-10-24 10:39:53 | 2019-10-24 10:39:53 |
+----+------------+--------------------+-----+---------------------+---------------------+
5 rows in set (0.00 sec)

# 删除id为5的记录,重启MySQL服务,再次插入id变成5(max(id)+1)
mysql> select * from employees;
+----+------------+--------------------+-----+---------------------+---------------------+
| id | name | address | age | gmt_create | gmt_modified |
+----+------------+--------------------+-----+---------------------+---------------------+
| 1 | xujinliang | 山东省青岛市 | 32 | 2019-09-24 17:13:00 | 2019-09-24 17:13:00 |
| 2 | maofang | 山东省青岛市 | 32 | 2019-09-24 17:32:00 | 2019-09-24 17:40:49 |
| 3 | raohui | 陕西省西安市 | 29 | 2019-09-24 17:39:28 | 2019-09-24 17:39:28 |
| 4 | yangnan | NULL | 22 | 2019-09-24 17:39:52 | 2019-09-24 17:39:52 |
| 5 | liu | 山东省烟台市 | 22 | 2019-10-24 11:03:14 | 2019-10-24 11:03:14 |
+----+------------+--------------------+-----+---------------------+---------------------+
6 rows in set (0.00 sec)

mysql> select * from employees_myisam;
+----+------------+--------------------+-----+---------------------+---------------------+
| id | name | address | age | gmt_create | gmt_modified |
+----+------------+--------------------+-----+---------------------+---------------------+
| 1 | xujinliang | 山东省青岛市 | 32 | 2019-09-24 17:13:00 | 2019-09-24 17:13:00 |
| 2 | maofang | 山东省青岛市 | 32 | 2019-09-24 17:32:00 | 2019-09-24 17:40:49 |
| 3 | raohui | 陕西省西安市 | 29 | 2019-09-24 17:39:28 | 2019-09-24 17:39:28 |
| 4 | yangnan | NULL | 22 | 2019-09-24 17:39:52 | 2019-09-24 17:39:52 |
| 5 | yuzhiqiang | 山东省青岛市 | 29 | 2019-10-24 10:39:53 | 2019-10-24 10:39:53 |
+----+------------+--------------------+-----+---------------------+---------------------+
5 rows in set (0.00 sec)

# 删除id为5的记录,重启MySQL服务,再次插入id变成6
mysql> select * from employees_myisam;
+----+------------+--------------------+-----+---------------------+---------------------+
| id | name | address | age | gmt_create | gmt_modified |
+----+------------+--------------------+-----+---------------------+---------------------+
| 1 | xujinliang | 山东省青岛市 | 32 | 2019-09-24 17:13:00 | 2019-09-24 17:13:00 |
| 2 | maofang | 山东省青岛市 | 32 | 2019-09-24 17:32:00 | 2019-09-24 17:40:49 |
| 3 | raohui | 陕西省西安市 | 29 | 2019-09-24 17:39:28 | 2019-09-24 17:39:28 |
| 4 | yangnan | NULL | 22 | 2019-09-24 17:39:52 | 2019-09-24 17:39:52 |
| 6 | yuzhiqiang | 山东省青岛市 | 29 | 2019-10-24 10:58:32 | 2019-10-24 10:58:32 |
+----+------------+--------------------+-----+---------------------+---------------------+
5 rows in set (0.00 sec)

主键的选择

① 从性能和存储空间方面考量,自增主键往往是更好的选择:

  • 存储空间少,自增主键一般用整型做主键,则二级索引叶子节点只要 4 个字节;而如果使用业务字段做主键,比如字符串类型的身份证号,每个二级索引的叶子节点得占用约 20 个字节。
  • 性能好,插入数据时,自增主键能保证有序插入,避免了页分裂。

② 自增 id 是整型字段,推荐使用 unsigned int 类型来定义增长主键,可以存储 42 亿数据。

类型 大小 范围(有符号) 范围(无符号)
int 4 byte (-2147483648, 2147483647) (0, 4294967295)
bigint 8 byte (-9223372036854775808, 9223372036854775807) (0, 18446744073709551615)

③ 自增 id 是增长的,但不一定连续,造成自增 id 不连续的情况可能有:

  • 唯一键冲突
  • 事务回滚
  • insert ... select 语句批量申请自增 id

Python · 网络编程

发表于 2019-10-24 | 更新于 2019-11-15 | 分类于 Python

简介

用 TCP 协议进行 Socket 编程在 Python 中十分简单,对于客户端,要主动连接服务器的 IP 和指定端口,对于服务器,要首先监听指定端口,然后,对每一个新的连接,创建一个线程或进程来处理。通常,服务器程序会无限运行下去。同一个端口,被一个 Socket 绑定了以后,就不能被别的 Socket 绑定了。

TCP编程

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/python
# -*- coding:utf-8 -*-
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接:
s.connect(('127.0.0.1', 9999))
# 接收欢迎消息:
print(s.recv(1024).decode('utf-8'))
for data in [b'Michael', b'Tracy', b'Sarah']:
# 发送数据:
s.send(data)
print(s.recv(1024).decode('utf-8'))
s.send(b'exit')
s.close()

服务端:

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
#!/usr/bin/python
# -*- coding:utf-8 -*-
import socket
import time
import threading

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 监听端口:
s.bind(('127.0.0.1', 9999))
s.listen(5)
print('Waiting for connection...')

def tcplink(sock, addr):
print('Accept new connection from %s:%s...' % addr)
sock.send(b'Welcome!')
while True:
data = sock.recv(1024)
time.sleep(1)
if not data or data.decode('utf-8') == 'exit':
break
sock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8'))
sock.close()
print('Connection from %s:%s closed.' % addr)

while True:
# 接受一个新连接:
sock, addr = s.accept()
# 创建新线程来处理TCP连接:
t = threading.Thread(target=tcplink, args=(sock, addr))
t.start()

执行结果:

1
2
3
4
5
6
7
8
Waiting for connection...
Accept new connection from 127.0.0.1:52501...
Connection from 127.0.0.1:52501 closed.

Welcome!
Hello, Michael!
Hello, Tracy!
Hello, Sarah!

UDP编程

TCP:是建立可靠连接,并且通信双方都可以以流的形式发送数据。

UDP:是面向无连接的协议,使用 UDP 协议时,不需要建立连接,只需要知道对方的 IP 地址和端口号,就可以直接发数据包。虽然用 UDP 传输数据不可靠,但它的优点是和 TCP 比速度快,对于不要求可靠到达的数据,就可以使用 UDP 协议。

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
#!/usr/bin/python
# -*- coding:utf-8 -*-
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for data in [b'Michael', b'Tracy', b'Sarah']:
# 发送数据:
s.sendto(data, ('127.0.0.1', 9999))
# 接收数据:
print(s.recv(1024).decode('utf-8'))
s.close()


#!/usr/bin/python
# -*- coding:utf-8 -*-
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定端口:
s.bind(('127.0.0.1', 9999))
print('Bind UDP on 9999...')

while True:
# 接收数据:
data, addr = s.recvfrom(1024)
print('Received from %s:%s.' % addr)
s.sendto(b'Hello, %s! ' % data, addr)

执行结果:

1
2
3
4
5
6
7
8
Bind UDP on 9999...
Received from 127.0.0.1:58248.
Received from 127.0.0.1:58248.
Received from 127.0.0.1:58248.

Hello, Michael!
Hello, Tracy!
Hello, Sarah!

小结:

UDP 的使用与 TCP 类似,但是不需要建立连接。此外,服务器绑定 UDP 端口和 TCP 端口互不冲突,也就是说,UDP 的 9999 端口与 TCP 的 9999 端口可以各自绑定。

Python · 正则表达式

发表于 2019-10-24 | 更新于 2019-10-28 | 分类于 Python

正则表达式是一种用来匹配字符串的强有力的武器,它的设计思想是用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则该字符串就是不合法的。

功能:

  • 校验字符串,判断是否匹配
  • 提取子字符串

字符

\d:表示可以匹配一个数字

\w:表示可以匹配一个字母或数字

.:表示可以匹配任意字符

*:表示任意个字符(包括0个)

+:表示至少一个字符

?:表示0个或1个字符

{n}:表示n个字符

{n,m}:表示n-m个字符

\s:表示可以匹配一个空格(也包括Tab等空白符),所以\s+表示至少有一个空格

[]:表示更精确地范围匹配

^:表示行的开头,^\d表示必须以数字开头

$:表示行的结束,\d$表示必须以数字结束

A|B:可以匹配A或B,所以(P|p)ython可以匹配'Python'或者'python'。

[0-9a-zA-Z\_]:可以匹配一个数字、字母或者下划线

[0-9a-zA-Z\_]+:可以匹配至少由一个数字、字母或者下划线组成的字符串,如'a100','0_Z','Py3000'等等

[a-zA-Z\_][0-9a-zA-Z\_]*:可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是 Python 合法的变量

[a-zA-Z\_][0-9a-zA-Z\_]{0, 19}:更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)

re 模块:Python 提供 re模块,包含所有正则表达式的功能,建议使用 r 前缀,就不用考虑转义的问题。

match() 方法:判断是否匹配,如果匹配成功,返回一个Match对象,否则返回None。

1
2
3
4
5
test = '用户输入的字符串'
if re.match(r'正则表达式', test):
print('ok')
else:
print('failed')

切分字符串

1
2
>>> re.split(r'\s+', 'a b   c')
['a', 'b', 'c']

分组:可以在Match对象上用group()方法提取出子串来,group(0)永远是原始字符串,group(1)、group(2)……表示第1、2、……个子串。

1
2
3
4
5
6
7
8
9
>>> m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')
>>> m
<_sre.SRE_Match object; span=(0, 9), match='010-12345'>
>>> m.group(0)
'010-12345'
>>> m.group(1)
'010'
>>> m.group(2)
'12345'

贪婪匹配:正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。

1
2
3
4
5
6
7
>>> re.match(r'^(\d+)(0*)$', '102300').groups()
('102300', '')

由于\d+采用贪婪匹配,直接把后面的0全部匹配了,结果0*只能匹配空字符串了。必须让\d+采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0匹配出来,加个?就可以让\d+采用非贪婪匹配:

>>> re.match(r'^(\d+?)(0*)$', '102300').groups()
('1023', '00')

编译:当我们在 Python 中使用正则表达式时,re模块内部会干两件事情:

1)编译正则表达式,如果正则表达式的字符串本身不合法,会报错。

2)用编译后的正则表达式去匹配字符串。

如果一个正则表达式要重复使用几千次,出于效率的考虑,我们可以预编译该正则表达式,接下来重复使用时就不需要编译这个步骤了,直接进行匹配。

1
2
3
4
5
6
7
8
>>> import re
# 编译:
>>> re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$')
# 使用:
>>> re_telephone.match('010-12345').groups()
('010', '12345')
>>> re_telephone.match('010-8086').groups()
('010', '8086')

Python · 进程与线程

发表于 2019-10-24 | 更新于 2019-10-28 | 分类于 Python

概念

目的:多核 CPU 时代,为了充分利用资源,依赖多任务并发编程技术提升程序性能和改善用户体验。

多任务并发编程方式:

  • 多进程
  • 多线程
  • 多进程 + 多线程

进程:操作系统中执行的一个程序,一个任务就是一个进程。

线程:线程是最小的执行单元,一个线程相当于进程的子任务。

进程间通信方式:管道、信号、套接字、共享内存区等。

多进程

  • os 模块:使用 fork() 调用实现多进程。
  • multiprocessing 模块:实现跨平台多进程。
  • Process 对象:实现多进程。
  • Pool 对象:可以用进程池的方式批量创建子进程,Pool 的默认大小是 CPU 的核数。
  • Queue 对象:通过队列实现进程间通信。
  • Pipes 对象:通过管道实现进程间通信。
  • start 方法:表示启动进程。
  • join 方法:表示等待进程执行结束。
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
from random import randint
from time import time, sleep


def download_task(filename):
print('开始下载%s...' % filename)
time_to_download = randint(5, 10)
sleep(time_to_download)
print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))


def main():
start = time()
download_task('Python从入门到进坑.pdf')
download_task('Peking Hot.avi')
end = time()
print('总共耗费了%.2f秒.' % (end - start))


if __name__ == '__main__':
main()


运行结果:
开始下载 Python从入门到进坑.pdf...
Python从入门到住院.pdf下载完成! 耗费了6秒
开始下载 Peking Hot.avi...
Peking Hot.avi下载完成! 耗费了7秒
总共耗费了13.01秒.
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
from multiprocessing import Process
from os import getpid
from random import randint
from time import time, sleep


def download_task(filename):
print('启动下载进程,进程号[%d].' % getpid())
print('开始下载%s...' % filename)
time_to_download = randint(5, 10)
sleep(time_to_download)
print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))


def main():
start = time()
p1 = Process(target=download_task, args=('Python从入门到住院.pdf', ))
p1.start()
p2 = Process(target=download_task, args=('Peking Hot.avi', ))
p2.start()
p1.join()
p2.join()
end = time()
print('总共耗费了%.2f秒.' % (end - start))


if __name__ == '__main__':
main()


运行结果:
启动下载进程,进程号[1530].
开始下载 Python从入门到住院.pdf...
启动下载进程,进程号[1531].
开始下载 Peking Hot.avi...
Peking Hot.avi下载完成! 耗费了7秒
Python从入门到住院.pdf下载完成! 耗费了10秒
总共耗费了10.01秒.

多线程

  • thread 模块:实现多线程
1
2
3
4
5
6
7
8
9
10
11
12
13
import os

print('Process (%s) start...' % os.getpid())
# Only works on Unix/Linux/Mac
pid = os.fork()
if pid == 0:
print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))
else:
print('I (%s) just created a child process (%s).' % (os.getpid(), pid))

Process (876) start...
I (876) just created a child process (877).
I am child process (877) and my parent is 876.

Python 的 multiprocessing模块就是跨平台版本的多进程模块。multiprocessing模块提供了一个Process类来代表一个进程对象,下面的例子演示了启动一个子进程并等待其结束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from multiprocessing import Process
import os

# 子进程要执行的代码
def run_proc(name):
print('Run child process %s (%s)...' % (name, os.getpid()))

if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Process(target=run_proc, args=('test',))
print('Child process will start.')
p.start()
p.join()
print('Child process end.')


执行结果如下:
Parent process 928.
Child process will start.
Run child process test (929)...
Child process end.

创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动,这样创建进程比fork()还要简单。join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。

Pool:如果要启动大量的子进程,可以用进程池的方式批量创建子进程,Pool的默认大小是 CPU 的核数。

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
from multiprocessing import Pool
import os, time, random

def long_time_task(name):
print('Run task %s (%s)...' % (name, os.getpid()))
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print('Task %s runs %0.2f seconds.' % (name, (end - start)))

if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Pool(4)
for i in range(5):
p.apply_async(long_time_task, args=(i,))
print('Waiting for all subprocesses done...')
p.close()
p.join()
print('All subprocesses done.')

执行结果如下:
Parent process 669.
Waiting for all subprocesses done...
Run task 0 (671)...
Run task 1 (672)...
Run task 2 (673)...
Run task 3 (674)...
Task 2 runs 0.14 seconds.
Run task 4 (673)...
Task 1 runs 0.27 seconds.
Task 3 runs 0.86 seconds.
Task 0 runs 1.41 seconds.
Task 4 runs 1.91 seconds.
All subprocesses done.

进程间通信:Process之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python 的multiprocessing 模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据。

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
from multiprocessing import Process, Queue
import os, time, random

# 写数据进程执行的代码:
def write(q):
print('Process to write: %s' % os.getpid())
for value in ['A', 'B', 'C']:
print('Put %s to queue...' % value)
q.put(value)
time.sleep(random.random())

# 读数据进程执行的代码:
def read(q):
print('Process to read: %s' % os.getpid())
while True:
value = q.get(True)
print('Get %s from queue.' % value)

if __name__=='__main__':
# 父进程创建Queue,并传给各个子进程:
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
# 启动子进程pw,写入:
pw.start()
# 启动子进程pr,读取:
pr.start()
# 等待pw结束:
pw.join()
# pr进程里是死循环,无法等待其结束,只能强行终止:
pr.terminate()

执行结果如下:
Process to write: 50563
Put A to queue...
Process to read: 50564
Get A from queue.
Put B to queue...
Get B from queue.
Put C to queue...
Get C from queue.
1…8910…18
Hui Rao

Hui Rao

最好的成长是分享
173 日志
19 分类
14 标签
GitHub E-Mail
© 2021 Hui Rao
由 Hexo 强力驱动 v3.9.0
|
主题 – NexT.Gemini v7.1.0
|