线程
CPU执行调度的最小单位。
不能独立存在,依赖进程存在。
一个进程至少有一个线程,叫做主线程,另外还有内核线程、用户线程。
线程之间共享内存。
线程之间的通信效率远高于进程间通信效率,线程之间切换代价也比进程小很多。
适用场景
Python的多线程适用于IO密集型任务。
多任务可以由多进程完成,也可以由一个进程内的多线程完成。
关系
进程是由若干线程组成的,一个进程至少有一个线程。
线程是通过一个进程派生出来的,进程结束,其派生的线程就全部死掉了,线程间共用进程的内存,线程奔溃也可能导致进程奔溃。
为什么需要线程锁
多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。
解决方案就是加锁。锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。
GIL--全局锁
GIL的全称是Global Interpreter Lock (全局解释器锁)。
Python解释器由于设计时有GIL全局锁,导致了多线程无法利用多核。
Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。
缺陷
频繁线程切换代价高。
线程安全(GIL)。
共享还是独享
如果某些资源不独享会导致线程运行错误,则该资源就由每个线程独享,而其他资源都由进程里面的所有线程共享。
线程共享资源:
地址空间、全局变量、打开的文件、子进程、闹铃、信号与信号服务程序、记账信息。
线程独享资源:
程序计数器、寄存器、栈、状态字。
python的多线程模块
Python的标准库提供了两个模块:thread和threading,thread是低级模块,threading是高级模块,对thread进行了封装。
绝大多数情况下,我们只需要使用threading这个高级模块。
多线程写法
1、导入模块
import threading
2、产生对象,传递的target参数是一个函数,即worker是一个函数;可以给线程传线程的name;可以给线程传参数args,agrs必须是一个元组。
t = threading.Thread(target=worker,name='lyz',args=(1,))
3、执行线程
t.start()
threading 模块提供的方法
threading.currentThread(): 返回当前的线程变量。
threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
run(): 用以表示线程活动的方法。
start():启动线程活动。
join(): 等待至线程中止。join()的作用是,在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
isAlive(): 返回线程是否活动的。
getName(): 返回线程名。
setName(): 设置线程名。
多线程的注意事项
1、线程执行是无序的
2、生命周期:
python中,主线程会等待子线程结束之后才结束。
但是如果设置d.setDaemon(True)这个属性,则主线程结束,子线程就必须结束了。
3、阻塞:
如果你想让主线程等所有子线程执行完才执行主线程的任务:join()
t = threading.Thread(target=worker,name='lyz',args=(1,))
t.join()
4、默认情况下,多线程的无序性,操作全局变量是不安全的,所以有线程锁
from threading import Lock,Thread
然后生成锁对象:
lock = Lock()
申请锁:
lock.acquire()
释放锁:
lock.release()
代码例子
例1:最简单的多线程例子
#!/usr/bin/env python
# encoding: UTF-8
import threading
def worker():
print 'worker'
return
for i in range(5):
t = threading.Thread(target=worker)
t.start()
执行结果:几乎同时打印worker,而不是循环一次才打印一次
[root@salt1 threading]# python t1.py
worker
worker
worker
worker
worker
例2:带参数
#!/usr/bin/env python
# encoding: UTF-8
import threading
def worker(num, num2):
print 'worker: %d %d' % (num, num2)
return
for i in range(5):
t = threading.Thread(target=worker, args=(i, i)) #args必须是一个元组
t.start()
执行结果:
[root@salt1 threading]# python t2.py
worker: 1 1
worker: 2 2
worker: 3 3
worker: 4 4
例3:多线程ping多个ip
import threading
import os
def ping(ip):
os.system('ping -c 1 %s'%ip)
hostlist = ['192.168.50.20','192.168.50.24','192.168.50.13']
for i in hostlist:
t = threading.Thread(target=ping,args=(i,))
t.start()
执行结果:
[root@salt1 threading]# python t3.py
PING 192.168.50.20 (192.168.50.20) 56(84) bytes of data.
64 bytes from 192.168.50.20: icmp_seq=1 ttl=64 time=0.029 ms
--- 192.168.50.20 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.029/0.029/0.029/0.000 ms
PING 192.168.50.24 (192.168.50.24) 56(84) bytes of data.
64 bytes from 192.168.50.24: icmp_seq=1 ttl=64 time=0.374 ms
--- 192.168.50.24 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.374/0.374/0.374/0.000 ms
PING 192.168.50.13 (192.168.50.13) 56(84) bytes of data.
64 bytes from 192.168.50.13: icmp_seq=1 ttl=64 time=0.774 ms
--- 192.168.50.13 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.774/0.774/0.774/0.000 ms
例4:线程执行是无序的
#coding=utf8
import logging
import threading
import time
logging.basicConfig(level=logging.DEBUG,
format='[%(levelname)s](%(threadName)-10s) %(message)s',
)
def daemon():
logging.debug('Starting')
time.sleep(2)
logging.debug('Exiting')
d = threading.Thread(name='daemon', target=daemon)
#但是如果设置d.setDaemon(True)这个属性,则主线程结束,子线程就结束了
#d.setDaemon(True)
def non_daemon():
logging.debug('starting')
logging.debug('exiting')
t = threading.Thread(name='non-daemon', target=non_daemon)
d.start()
t.start()
#如果你想让主线程等所有子线程执行完才执行主线程的任务:join()
#d.join()
#t.join()
print "all done!"
例5:多线程的另外一种写法
#!/usr/bin/env python
import threading
import logging
logging.basicConfig(
level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
)
#类的写法,父类必须是threading.Thread,类里边的函数名必须是run,覆盖父类里边的run
class MyThread(threading.Thread):
def run(self):
logging.debug('running')
return
for i in range(5):
t = MyThread()
t.start()
例6:同上例子
import threading
import logging
logging.basicConfig(level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
)
class MyThreadWithArgs(threading.Thread):
def __init__(self, group=None, target=None, name=None,
args=(), kwargs=None, verbose=None):
#threading.Thread.__init__(self, group=group, target=target, name=name,
super(MyThreadWithArgs, self).__init__()
self.args = args
self.kwargs = kwargs
return
def run(self):
logging.debug('running with %s and %s', self.args, self.kwargs)
return
for i in range(5):
t = MyThreadWithArgs(args=(i,), kwargs={'a':'A', 'b':'B'})
t.start()
例7:为什么需要线程锁
首先看没有线程锁的例子:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
#race condition
from threading import Thread
var = 0
class incrementThread(Thread):
def run(self):
global var
read_value = var
print "var in %s is %d" % (self.name, var)
var = read_value + 1
print "var in %s after increment is %d" % (self.name, var)
def use_increment_thread():
threads = []
#尝试加大循环的次数就会到最后的结果小于循环的次数
#这是因为当不同的线程同时操作var+1 造成的,例如当t1,t2同时读到的var为10,他们分别加1那最后的结果是11,而不是我们想要的12
for i in range(5000):
t = incrementThread()
threads.append(t)
t.start()
for t in threads:
t.join()
print "After 5000 modifications, var is %d" % (var)
if __name__ == '__main__':
use_increment_thread()
执行结果:省略一大部分
[root@teach threading]$ python t2.py
var in Thread-4998 after increment is 4901
var in Thread-4999 is 4901
var in Thread-4999 after increment is 4902
var in Thread-5000 is 4902
var in Thread-5000 after increment is 4903
After 5000 modifications, var is 4903
意思是产生了5000个子线程,但是var只累加到了4903。
这就是多线程是无序的。var=10的时候,可能产生了2个子线程同时对一个var累加,那么的出来的var=11,而不是var=12
例8:线程锁
接下来看看加了进程锁的例子:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from threading import Lock, Thread
#生成锁对象
lock = Lock()
some_var = 0
class IncrementThread(Thread):
def run(self):
global some_var
#申请锁
lock.acquire()
read_value = some_var
print "some_var in %s is %d" % (self.name, read_value)
some_var = read_value + 1
print "some_var in %s after increment is %d" % (self.name, some_var)
#释放锁
lock.release()
def use_increment_thread():
threads = []
for i in range(5000):
t = IncrementThread()
threads.append(t)
t.start()
for t in threads:
t.join()
print "After 5000 modifications, some_var should have become 5000"
print "After 5000 modifications, some_var is %d" % (some_var,)
use_increment_thread()
执行结果:只截取最后面一部分
[root@teach threading]$ python t2.py
some_var in Thread-4998 after increment is 4998
some_var in Thread-4999 is 4998
some_var in Thread-4999 after increment is 4999
some_var in Thread-5000 is 4999
some_var in Thread-5000 after increment is 5000
After 5000 modifications, some_var should have become 5000
After 5000 modifications, some_var is 5000
这就是线程锁的效果了,产生了5000个子线程,var也累加了5000次。
例9:线程锁,函数写法
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from threading import Lock, Thread
# 生成锁对象
lock = Lock()
some_var = 0
def run(num):
global some_var
# 申请锁
lock.acquire()
read_value = some_var
print("some_var in %s is %d" % (num, read_value))
some_var = read_value + 1
print("some_var in %s after increment is %d" % (num, some_var))
# 释放锁
lock.release()
def use_increment_thread():
threads = []
for i in range(5000):
t = Thread(target=run, args=(i,))
threads.append(t)
t.start()
for t in threads:
t.join()
print("After 5000 modifications, some_var should have become 5000")
print("After 5000 modifications, some_var is %d" % (some_var,))
if __name__ == "__main__":
use_increment_thread()