python&&多线程多进程及主机管理&&学习笔记
- 多线程知识回顾
- 关于python"假线程"的说法
- python是非线程安全的语言
- 基本多进程示例
- 基本多进程示例2
- 多进程之进程间通讯示例1
- 多进程间内存共享示例1
- 多进程之进程间通讯示例2
- 多进程间内存共享示例2
- 多进程间内存共享示例3
- 进程池Pool
- 利用pool产生多进程示例
- 多进程多线程执行示例1
- IT审计
- 堡垒机的开发示例
- 审计堡垒机的安全控制
- shellinaboxd
- 异步
- Select 、 poll & epoll(异步IO模型)
- select通过单进程同时处理多个非阻塞的socket连接示例
多线程知识回顾
关于python"假线程"的说法
因为存在全局解释器锁的原因, 虽然python的程序看上去有多个线程同时工作,但其实同一时刻,仅有一个线程在CPU上工作,所以有python假线程的说法。
在IO密集型的业务中,使用多线程比单线程速度要快,因为单线程等待时间长;在CPU密集型的业务中,使用多线程不一定比单线程效率要高,因为线程的切换需要消耗大量资源。
python是非线程安全的语言
python是非线程安全的,需要程序员主动去添加控制。即程序员如果没有对python程序添加线程安全控制,多个线程同时工作时会同时对一份数据进行操作,最终导致数据混乱。因此需要添加线程锁保证数据的准确性(同一时刻只存在唯一一个对数据进行操作的线程)。
因为GIL(全局解释器锁)的存在,python中的多线程是存在弊端的,其不能利用多核的优势?
基本多进程示例
multiprocessingDemo.py
#!/usr/bin/env python
#coding:utf-8
#导入进程池Pool类
from
multiprocessing
import
Pool
import
time
#简单的多线程多进程示例
def
f
(
x
)
:
time
.
sleep
(
1
)
# print(x)
print
(
x
*
x
)
if
__name__
==
"__main__"
:
#实例化Pool,里面最多放5个进程。
p
=
Pool
(
5
)
#map(函数,列表):以串行的形式(按序单个执行),将列表内的每一个值传入函数,最后返回列表
#p.map(函数,列表):以多进程的形式(并发执行),将列表内的每一个值传入函数,最后返回列表
print
(
p
.
map
(
f
,
range
(
10
)
)
)
基本多进程示例2
multiprocessingDemo2.py
#!/usr/bin/env python
#coding:utf-8
from
multiprocessing
import
Process
import
os
def
info
(
title
)
:
print
(
title
)
print
(
'module name:'
,
__name__
)
if
hasattr
(
os
,
'getppid'
)
:
#获取父进程的pid
print
(
'parent process:'
,
os
.
getppid
(
)
)
print
(
'process id :'
,
os
.
getpid
(
)
)
def
f
(
name
)
:
info
(
'function f'
)
print
(
'hello,'
,
name
)
if
__name__
==
'__main__'
:
#在主(父)进程调用函数info
info
(
'main file'
)
print
(
'----------'
)
#启动一个子进程p.进程p调用函数f,函数f调用函数info
p
=
Process
(
target
=
f
,
args
=
(
'bob'
,
)
)
p
.
start
(
)
p
.
join
(
)
#如上所示,主进程与子进程同时运行。子进程的ppid为父进程的pid.
#子进程会完全复制父进程的内存空间,多(n)进程占用的内存空间是单进程的n倍。
#每一个进程至少会有一个(主)线程。
多进程之进程间通讯示例1
如下图代码段和log所示,启动10个进程往列表info里面插入数据,此时列表info的数据仅存在一个,即多进程之间内存数据相互独立;启动10个线程往列表info里面插入数据,此时列表info的数据有十个,即多线程之间内存数据共享。
#!/usr/bin/env python
#coding:utf-8
from
multiprocessing
import
Process
import
threading
import
time
def
run
(
info_list
,
n
)
:
info_list
.
append
(
n
)
print
(
info_list
)
info
=
[
]
#启动十个进程
if
__name__
==
"__main__"
:
for
i
in
range
(
10
)
:
p
=
Process
(
target
=
run
,
args
=
[
info
,
i
]
)
p
.
start
(
)
time
.
sleep
(
3
)
print
(
'------threading--------'
)
for
i
in
range
(
10
)
:
t
=
threading
.
Thread
(
target
=
run
,
args
=
[
info
,
i
]
)
t
.
start
(
)
'''
log:
[0]
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]
------threading--------
[0]
[0, 1]
[0, 1, 2]
[0, 1, 2, 3]
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4, 5]
[0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 4, 5, 6, 7]
[0, 1, 2, 3, 4, 5, 6, 7, 8]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
'''
多进程间内存共享示例1
如下图所示,借助queue模块可以实现多进程间的通讯
queue内的数据在进程间是统一的。因为此时的queue是从multiprocessing中引入的(经过multiprocessing封装过的),
#!/usr/bin/env python
#coding:utf-8
#导入模块,其中Process,Queue均属于multiprocessing内部的类
from
multiprocessing
import
Process
,
Queue
def
f
(
q
,
n
)
:
q
.
put
(
[
n
,
'hello'
]
)
#启动十个进程
if
__name__
==
"__main__"
:
q
=
Queue
(
)
for
i
in
range
(
10
)
:
p
=
Process
(
target
=
f
,
args
=
[
q
,
i
]
)
p
.
start
(
)
for
i
in
range
(
10
)
:
print
(
q
.
get
(
)
)
多进程之进程间通讯示例2
#!/usr/bin/env python
#coding:utf-8
#导入模块,其中Process,Queue均属于multiprocessing内部的类
from
multiprocessing
import
Process
,
Lock
def
f
(
l
,
i
)
:
#使用进程锁可以控制多进程使用同一屏幕的打印输出
#此时的lock完全克隆自threading里面的lock,/
l
.
acquire
(
)
print
(
'hello,world'
,
i
)
l
.
release
(
)
if
__name__
==
"__main__"
:
lock
=
Lock
(
)
for
num
in
range
(
10
)
:
Process
(
target
=
f
,
args
=
(
lock
,
num
)
)
.
start
(
)
'''
log:
hello,world 1
hello,world 3
hello,world 0
hello,world 2
hello,world 4
hello,world 5
hello,world 6
hello,world 7
hello,world 8
hello,world 9
'''
多进程间内存共享示例2
多进程间可以通过queue/array/value实现进程间的内存共享。
#!/usr/bin/env python
#coding:utf-8
from
multiprocessing
import
Value
,
Array
,
Process
def
f
(
n
,
a
)
:
n
.
value
=
3.1415926
for
i
in
range
(
5
)
:
#对Array列表的按索引排列的前5个元素取反
a
[
i
]
=
-
a
[
i
]
if
__name__
==
"__main__"
:
#生成Value对象,'d'代表有小数点的数字
num
=
Value
(
'd'
,
0.0
)
#生成Array对象,'i'代表整数,range(10)代表生成1到10之间的列表
arr
=
Array
(
'i'
,
range
(
10
)
)
#创建执行子进程p,执行完成后,父进程中的value和array对象的值随子进程p的操作而改变,即通过value和array可以实现多进程间的内存数据交互。
p
=
Process
(
target
=
f
,
args
=
(
num
,
arr
)
)
p
.
start
(
)
p
.
join
(
)
print
(
num
.
value
)
#为什么在方括号内部加冒号?因为如果不加冒号,该行代码将会报错(打印一个实例出来?)。
print
(
arr
[
:
]
)
'''
log:
3.1415926
[0, -1, -2, -3, -4, 5, 6, 7, 8, 9]
'''
多进程间内存共享示例3
#!/usr/bin/env python
#coding:utf-8
#使用Manager模块实现多进程间内存数据同步
from
multiprocessing
import
Process
,
Manager
def
f
(
d
,
l
)
:
d
[
1
]
=
'1'
d
[
'2'
]
=
2
d
[
0.25
]
=
None
#对字典列表元素进行反转
l
.
reverse
(
)
if
__name__
==
"__main__"
:
#实例化
manager
=
Manager
(
)
#父进程声明manager.字典
d
=
manager
.
dict
(
)
#父进程声明manager.列表
l
=
manager
.
list
(
range
(
10
)
)
print
(
'before process p :'
,
d
)
print
(
'before process p :'
,
l
)
#创建子进程,子进程调用函数f对manager.字典填充数据,对manager.列表进行翻转。
p
=
Process
(
target
=
f
,
args
=
(
d
,
l
)
)
p
.
start
(
)
p
.
join
(
)
#父进程打印manager.字典和manager.列表,此时数据均被子进程执行函数f改变。
print
(
'after process p :'
,
d
)
print
(
'after process p :'
,
l
)
'''
log:
before process p : {}
before process p : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
after process p : {1: '1', '2': 2, 0.25: None}
after process p : [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
'''
进程池Pool
利用pool产生多进程示例
#!/usr/bin/env python
#coding:utf-8
from
multiprocessing
import
Pool
import
time
def
f
(
x
)
:
print
(
x
*
x
)
time
.
sleep
(
1
)
return
x
*
x
if
__name__
==
"__main__"
:
#"processes=4"代表最大能同时运行四个进程
pool
=
Pool
(
processes
=
4
)
resList
=
[
]
for
i
in
range
(
10
)
:
#异步执行。一次性将10个进程的实例启动并放置在pool内。
#"res = pool.apply(f,[i,])"为同步执行(串行执行)
res
=
pool
.
apply_async
(
f
,
[
i
,
]
)
#将实例化成的对象存储到列表中
resList
.
append
(
res
)
#从列表中取执行的结果.设置timeout超时时间,超过超时时间则报超时错误。
for
i
in
resList
:
print
(
i
.
get
(
timeout
=
5
)
)
#查看启动结果。res.get()方法会等着进程执行完毕然后返回结果,会导致阻塞及多进程串行启动,因此该代码位置需放置在循环外部。如上所示。
#res.get()
#使用pool.map可以向结果返回到列表内
print
(
pool
.
map
(
f
,
range
(
10
)
)
)
'''
log;
0
1
4
9
16
0
25
1
36
4
49
9
64
16
81
25
36
49
64
81
0
1
4
9
16
25
36
49
64
81
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
'''
p.start()和p.join()
start()和join()一起使用,可以令并发执行的多进程达到串行执行的效果。join()带有等待的作用。
p.join()与res.get()与pool.apply(f,[i,])均会因为存在等待结果的特性而导致在多进程执行时出现串行执行的效果。
同时并发跑多少个进程是合适的?
看cpu的核数,一般并发进程数量等于cpu的核数(或两倍)即可。过多容易导致出现僵尸进程,即父进程与子进程失联。出现僵尸进程后,子进程处于不受管理状态,最后被操作系统的根进程回收,回收后会处于闲置无法杀死的状态。唯一的方法是等待机器重启或因为特殊原因子进程消失。
ps 命令中的defunct状态即代表僵尸进程状态,僵尸进程过多会对操作系统造成很大的压力。
以导致僵尸进程出现的用户身份登录服务器去杀死僵尸进程会不会成功?
多进程多线程执行示例1
#!/usr/bin/env python
#coding:utf-8
from
multiprocessing
import
Pool
import
time
import
threading
import
os
def
threadFun
(
m
,
n
)
:
print
(
'第 %s个进程创建了第 %s个线程,进程pid为%s:'
,
(
m
,
n
,
os
.
getpid
(
)
)
)
def
f
(
x
)
:
for
i
in
range
(
3
)
:
t
=
threading
.
Thread
(
target
=
threadFun
,
args
=
(
x
,
i
)
)
t
.
start
(
)
print
(
'第'
,
x
,
'个进程开始创建线程啦'
)
if
__name__
==
"__main__"
:
#"processes=4"代表最大能同时运行四个进程
pool
=
Pool
(
processes
=
4
)
resList
=
[
]
for
i
in
range
(
3
)
:
#异步执行。一次性将10个进程的实例启动并放置在pool内。
#"res = pool.apply(f,[i,])"为同步执行(串行执行)
res
=
pool
.
apply_async
(
f
,
[
i
,
]
)
#将实例化成的对象存储到列表中
resList
.
append
(
res
)
#从列表中取执行的结果.设置timeout超时时间,超过超时时间则报超时错误。
for
i
in
resList
:
print
(
i
.
get
(
timeout
=
5
)
)
#查看启动结果。res.get()方法会等着进程执行完毕然后返回结果,会导致阻塞及多进程串行启动,因此该代码位置需放置在循环外部。如上所示。
#res.get()
#使用pool.map可以向结果返回到列表内
#print(pool.map(f,range(10)))
'''
log:
第 %s个进程创建了第 %s个线程,进程pid为%s: (0, 0, 193136)
第 0 个进程开始创建线程啦
第 %s个进程创建了第 %s个线程,进程pid为%s: (0, 1, 193136)
第 0 个进程开始创建线程啦
第 0 个进程开始创建线程啦
None
第 %s个进程创建了第 %s个线程,进程pid为%s: (0, 2, 193136)
第 1 个进程开始创建线程啦
第 %s个进程创建了第 %s个线程,进程pid为%s: (1, 0, 193136)
第 1 个进程开始创建线程啦
第 %s个进程创建了第 %s个线程,进程pid为%s: (1, 1, 193136)
第 1 个进程开始创建线程啦
None
第 %s个进程创建了第 %s个线程,进程pid为%s: (1, 2, 193136)
第 %s个进程创建了第 %s个线程,进程pid为%s: (2, 0, 193136)
第 2 个进程开始创建线程啦
第 2 个进程开始创建线程啦
第 %s个进程创建了第 %s个线程,进程pid为%s: (2, 1, 193136)
第 2 个进程开始创建线程啦
None
第 %s个进程创建了第 %s个线程,进程pid为%s: (2, 2, 193136)
'''
IT审计
堡垒机的开发示例
作用:
为了安全起见,必须在防火墙上做限制,运维区用户仅能访问登录堡垒机,堡垒机能访问机房服务器。即堡垒机是运维区用户操作机房服务器的唯一接口。
interactive.py
def
posix_shell
(
chan
)
:
import
select
oldtty
=
termios
.
tcgetattr
(
sys
.
stdin
)
#打开文件夹,存储列表records内的数据
f
=
file
(
'records.txt'
,
'ab+'
)
try
:
tty
.
setraw
(
sys
.
stdin
.
fileno
(
)
)
tty
.
setcbreak
(
sys
.
stdin
.
fileno
(
)
)
chan
.
settimeout
(
0.0
)
#定义一个列表,存放用户在Linux服务器上执行的命令
records
=
[
]
while
True
:
r
,
w
,
e
=
select
.
select
(
[
chan
,
sys
.
stdin
]
,
[
]
,
[
]
)
if
chan
in
r
:
try
:
x
=
u
(
chan
.
recv
(
1024
)
)
if
len
(
x
)
==
0
:
sys
.
stdout
.
write
(
"\r\n*** EOF\r\n"
)
break
sys
.
stdout
.
write
(
x
)
sys
.
stdout
.
flush
(
)
except
socket
.
timeout
:
pass
if
sys
.
stdin
in
r
:
x
=
sys
.
stdin
.
read
(
1
)
#在循环代码中,使用append将用户输入的每一个字符依次存入列表records内
records
.
append
(
x
)
#如果用户敲击回车符
if
x
==
'\r'
:
#拼接列表records并显示
f
.
write
(
''
.
join
(
records
)
.
replace
(
'\r'
,
'\n'
)
)
#置空列表records,重新存储用户下一次执行的命令
records
=
[
]
if
len
(
x
)
==
0
:
break
chan
.
send
(
x
)
finally
:
termios
.
tcsetattr
(
sys
.
stdin
,
termios
.
TCSADRAIN
,
oldtty
)
f
.
close
(
)
审计堡垒机的安全控制
操作步骤:
#1.在linux服务器上创建新用户,并修改用户的环境变量文件
root@AY1402251551519263aeZ:~
# vim /home/opensips/.bashrc
#2.修改用户的环境变量文件,清空其他不需要的部分,添加以下python文件,实现该用户身份一经登录服务器便执行固定python文件,执行结束后立即登出服务器。即用户无法随意登录服务器随意进行自由操作。
echo
"================================================="
#sudo /usr/bin/python /usr/local/Triaquae2/bin/TriAquae_console
/usr/bin/python /home/audit_agent/menu.py
logout
#3.新用户远程登录堡垒机
#4.选择机房服务器
#5.登出堡垒机
shellinaboxd
一款基于web的shell软件
1.编译安装
2.无证书状态启动
./shellinaboxd -t
3.web页面打开http://xxx.xxx.xxx.xxx:4200远程登录服务器
异步
Select 、 poll & epoll(异步IO模型)
Select
一个线程实现并发操作需要使用异步执行。
在Linux 服务器上使用"ulimit -n "默认显示的是终端同时打开的最大文件数,也是select能监控的最大文件句柄数。select不断的在循环监控文件句柄,不断的将数据从内核态复制到用户态。文件句柄数越大,真正在工作的文件越少,select的效益就越低。
python的select()方法直接调用操作系统的IO接口,它监控sockets,open files,and pipes(所以带fileno)方法的文件句柄何时变为readable和writeable,或者通讯错误,select使得到同时监控多个连接变得简单。并且这比写一个长循环来等待和监控多客户端连接更高效,因为select直接通过操作系统提供的c的网络接口进行操作,而不是通过python解释器。
poll
poll没有最大连接数限制,缺点:包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而增加。
另外,select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll()的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,这种方式称为水平触发。
epoll
属于由内核直接支持的实现方法。epoll可以同时支持水平触发和边缘触发(只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要高一些,但代码实现相当复杂。
epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符是,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
另一个本质的改进在于epoll采用了基于事件的就绪通知方式,在select和poll中,进程只有在调用一定的方法后,内核才会对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
select通过单进程同时处理多个非阻塞的socket连接示例
sockSelect.py
#!/usr/bin/env python
#coding:utf-8
import
select
import
socket
import
sys
import
queue
#create a TCP/IP socket
server
=
socket
.
socket
(
socket
.
AF_INET
,
socket
.
SOCK_STREAM
)
#设置为非阻塞模式
server
.
setblocking
(
0
)
#bind the socket to the port
server_address
=
(
'localhost'
,
10000
)
print
(
sys
.
stderr
,
'starting up on %s port %s'
)
server
.
bind
(
server_address
)
#listen for incoming connections
#maximun number of connections 5
server
.
listen
(
5
)
#select同时监控三个 通信列表
#第一个:所有的输入的data,就是指外部发过来的数据
#sockets from which we except to read
inputs
=
[
server
]
#第二个:监控和接收所有要发出去的data
#sockets to which we expect to write
outputs
=
[
]
#第三个:监控错误信息
#所有客户端的进来的连接和数据将会被server的主循环放在上面的list中处理,我们现在的server端需要等待连接可写之后才能过来,
#然后接收数据并返回(因此不是在接收数据之后就立刻返回),因为每个连接要把输入或输出的数据先缓存到queue里,然后再由select取出来再发出去。
#outgoing message queue (socket:queue)
#定义空字典message_queue
message_queue
=
{
}
while
inputs
:
print
(
'waiting for the next event'
)
readable
,
writable
,
exceptional
=
select
.
select
(
inputs
,
outputs
,
inputs
)
#当你把inputs、outputs、exceptional(这里与inputs共用)传给select之后,它返回3个新的list,
#我们上面把他们分别赋值为readable、writable、exceptional,所有在readable list 中的socket代表有数据可接受,
#所有在writable list 中的存放着你可以对其进行发送操作的socket连接,当通道出现error时会把error写到exception列表中
#readable list 中的socket可以有三种可能状态,第一种是如果这个socket是main "server" socket,它负责监听客户端的连接,
#如果这个main server socket出现在readable 里面,那代表这是server端已经ready来接收一个新的连接进来了,为了让这个main server能同时处理多个连接,
#在下面的代码里,我们把这main server 的 socket设置为非阻塞模式。
#循环处理readable里面的数据
for
s
in
readable
:
#如果s是一个客户端对象
if
s
is
server
:
#返回客户端对象connection,和客户端地址client_address
connection
,
client_address
=
s
.
accept
(
)
print
(
sys
.
stderr
,
'new connection from '
,
client_address
)
#排队,形成类似非阻塞模式
connection
.
setblocking
(
0
)
#将客户端对象放入inputs列表内
inputs
.
append
(
connection
)
#为字典message_queu赋值,key为客户端对象connection,value为队列对象(存放客户端发送过来的数据)
message_queue
[
connection
]
=
queue
.
Queue
(
)
#如果s不是客户端对象,而是插入到字典内的客户端对象,则接受客户端对象数据并存储到字典内部
else
:
data
=
s
.
recv
(
1024
)
if
data
:
print
(
sys
.
stderr
,
'received "%s" from %s'
%
(
data
,
client_address
)
)
#取客户端数据存放在字典内部
message_queue
[
s
]
.
put
(
data
)
#判断已经存放了客户端数据的客户端对象是否在outputs列表中,若没有,将该客户端对象插入outputs列表中。
if
s
not
in
outputs
:
outputs
.
append
(
s
)
else
:
print
(
sys
.
stderr
,
'closing'
,
client_address
)
if
s
in
outputs
:
outputs
.
remove
(
s
)
inputs
.
remove
(
s
)
s
.
close
(
)
del
message_queue
[
s
]
for
s
in
writable
:
try
:
#对照writable列表从字典message_queue里面取数据(非阻塞模式,字典没空就抛异常再次重复轮训)
next_msg
=
message_queue
[
s
]
.
get_nowait
(
)
except
queue
.
Empty
:
print
(
sys
.
stderr
,
'output queue for '
,
s
.
getpeername
(
)
)
outputs
.
remove
(
s
)
else
:
print
(
sys
.
stderr
,
'sending "%s" to %s'
%
(
next_msg
,
client_address
)
)
s
.
send
(
next_msg
.
upper
(
)
)
for
s
in
exceptional
:
print
(
sys
.
stderr
,
'handling exceptional condition for'
)
inputs
.
remove
(
s
)
if
s
in
outputs
:
outputs
.
remove
(
s
)
s
.
close
(
)
del
message_queue
[
s
]
#
sockSelectClient.py
#!/usr/bin/env python
#coding:utf-8
import
socket
import
sys
messages
=
[
'this is a message'
,
'it will be sent'
,
'in parts'
,
]
server_address
=
(
'localhost'
,
10000
)
socks
=
[
socket
.
socket
(
socket
.
AF_INET
,
socket
.
SOCK_STREAM
)
,
socket
.
socket
(
socket
.
AF_INET
,
socket
.
SOCK_STREAM
)
,
]
print
(
sys
.
stderr
,
'connecting to %s port %s'
%
server_address
)
for
s
in
socks
:
s
.
connect
(
server_address
)
for
message
in
messages
:
for
s
in
socks
:
print
(
sys
.
stderr
,
'%s:sending "%s"'
%
(
s
.
getsockname
(
)
,
message
)
)
s
.
send
(
message
)
for
s
in
socks
:
data
=
s
.
recv
(
1024
)
print
(
sys
.
stderr
,
'%s:receives "%s"'
%
(
s
.
getsockname
(
)
,
data
)
)
if
not
data
:
print
(
sys
.
stderr
,
'closing socket'
,
s
.
getsockname
(
)
)