首先我们区分一下线程和进程:
进程和线程的关系:
(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。
(3)处理机分给线程,即真正在处理机上运行的是线程
(4)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。线程是指进程内的一个执行单元,也是进程内的可调度实体.
进程与线程的区别:
(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.
(4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。
多线程类似于同时执行多个不同程序,多线程运行有如下优点:
使用线程可以把占据长时间的程序中的任务放到后台去处理。
用户界面可以更加吸引人,比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。
程序的运行速度可能加快。
在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。
这里说明一下thread 模块已被废弃。用户可以使用 threading 模块代替。所以,在 Python3 中不能再使用"thread" 模块。为了兼容性,Python3 将 thread 重命名为 “_thread”。
Python创建Thread对象语法如下:
import threading
threading.Thread(target=None, name=None, args=())
主要参数说明:
target 是函数名字,需要调用的函数。
name 设置线程名字。
args 函数需要的参数,以元祖( tuple)的形式传入
Thread 对象主要方法说明:
run(): 用以表示线程活动的方法。
start():启动线程活动。
join(): 等待至线程中止。
isAlive(): 返回线程是否活动的。
getName(): 返回线程名。
setName(): 设置线程名。
Python中实现多线程有两种方式:函数创建线程和创建线程类
函数创建线程:
import threading
import time, random, math
# idx 循环次数
def printNum(idx):
for num in range(idx):
# 打印当前运行的线程名字
print("{0}\tnum={1}".format(threading.current_thread().getName(), num))
# 随机生成一个数*2,向上取整使线程休眠,这里*2是为了时间间隔大一些
delay = math.ceil(random.random() * 2)
#进行休眠
time.sleep(delay)
if __name__ == '__main__':
#这里args()的值就是printNum函数的参数
th1 = threading.Thread(target=printNum, args=(2,), name="thread1")
th2 = threading.Thread(target=printNum, args=(3,), name="thread2")
# 启动2个线程
th1.start()
th2.start()
# 等待至线程中止
th1.join()
th2.join()
print("{0} 线程结束".format(threading.current_thread().getName()))
创建线程的时候,只需要传入一个执行函数和函数的参数即可完成threading.Thread实例的创建。
运行结果:
thread1 num=0
thread2 num=0
thread1 num=1
thread2 num=1
thread2 num=2
MainThread 线程结束
创建线程类:
import threading
import time, random, math
#继承Thread类
class MutliThread(threading.Thread):
def __init__(self, threadName, num):
threading.Thread.__init__(self)
self.name = threadName
self.num = num
#重写run方法
def run(self):
for i in range(self.num):
print("{0} i={1}".format(threading.current_thread().getName(), i))
delay = math.ceil(random.random() * 2)
time.sleep(delay)
if __name__ == '__main__':
thr1 = MutliThread("thread1", 3)
thr2 = MutliThread("thread2", 2)
# 启动线程
thr1.start()
thr2.start()
# 等待至线程中止
thr1.join()
thr2.join()
print("{0} 线程结束".format(threading.current_thread().getName()))
直接创建threading.Thread的子类来创建一个线程对象,实现多线程。通过继承Thread类,并重写Thread类的run()方法,在run()方法中定义具体要执行的任务。
运行结果:
thread1 i=0
thread2 i=0
thread1 i=1
thread2 i=1
thread1 i=2
MainThread 线程结束
守护线程:
上例中的join方法的作用是阻塞,等待子线程结束,join方法有一个参数是timeout,即如果主线程等待timeout,子线程还没有结束,则主线程强制结束子线程。简单来说就是使主线程等待子线程结束后再结束。如果子线程不使用join()函数,主线程和子线程是并行运行的,没有依赖关系,主线程执行了,子线程也在执行。
在多线程开发中,如果子线程设定为了守护线程,守护线程会等待主线程运行完毕后被销毁。一个主线程可以设置多个守护线程,守护线程运行的前提是,主线程必须存在,如果主线程不存在了,守护线程会被销毁。
import threading, time
def run(taskName):
print("任务:", taskName)
time.sleep(2)
print("{0} 任务执行完毕".format(taskName)) # 查看每个子线程
if __name__ == '__main__':
start_time = time.time()
for i in range(3):
thr = threading.Thread(target=run, args=("task-{0}".format(i),))
thr.start()
# 查看主线程和当前活动的所有线程数,active_count方法是查看当前线程数
print("{0}线程结束,当线程数量={1}".format(threading.current_thread().getName(), threading.active_count()))
print("消耗时间:", time.time() - start_time)
运行结果:
任务: task-0
任务: task-1
任务: task-2
MainThread线程结束,当线程数量=4
消耗时间: 0.0009751319885253906
task-2 任务执行完毕
task-0 任务执行完毕
task-1 任务执行完毕
从返回结果可以看出,当前的线程个数是4,线程个数=主线程数 + 子线程数,在本例中有1个主线程和3个子线程。主线程执行完毕后,等待子线程执行完毕,程序才会退出。
在这里我们把所有的子线程都设置为守护线程。子线程变成守护线程后,只要主线程执行完毕,程序不管子线程有没有执行完毕,程序都会退出。使用线程对象的setDaemon(True)函数来设置守护线程。
import threading, time
def run(taskName):
print("任务:", taskName)
time.sleep(2)
print("{0} 任务执行完毕".format(taskName))
if __name__ == '__main__':
start_time = time.time()
for i in range(3):
thr = threading.Thread(target=run, args=("task-{0}".format(i),))
# 把子线程设置为守护线程,一定在启动线程前设置
thr.setDaemon(True)
thr.start()
# 查看主线程和当前活动的所有线程数
thrName = threading.current_thread().getName()
thrCount = threading.active_count()
print("{0}线程结束,当线程数量={1}".format(thrName, thrCount))
print("消耗时间:", time.time() - start_time)
运行结果:
任务: task-0
任务: task-1
任务: task-2
MainThread线程结束,当线程数量=4
消耗时间: 0.0010023117065429688
这里就可以看出,设置了守护线程以后当主线程结束了,子线程也随之销毁。
线程互斥锁同步线程
import threading
balance = 100
def change(num, counter):
global balance
for i in range(counter):
balance += num
balance -= num
if balance != 100:
# 如果输出这句话,说明线程不安全
print("balance=%d" % balance)
break
if __name__ == "__main__":
thr1 = threading.Thread(target=change, args=(100, 500000), name='t1')
thr2 = threading.Thread(target=change, args=(100, 500000), name='t2')
thr1.start()
thr2.start()
thr1.join()
thr2.join()
print("{0} 线程结束".format(threading.current_thread().getName()))
运行结果:
balance=200
MainThread 线程结束
本例中在for循环的过程中,由于线程同时进行,我们定义的全局变量balance也同时被调用,只要循环次数足够,一定会有一次在t1执行balance += num后t2也执行balance += num,此时balance值为300,然后t1在进行balance -= num,此时balance值为200,进入if循环。
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
#创建锁
mutex = threading.Lock()
#锁定
mutex.acquire([timeout])
#释放
mutex.release()
import threading
balance = 100
lock = threading.Lock()
def change(num, counter):
global balance
for i in range(counter):
# 先要获取锁
lock.acquire()
balance += num
balance -= num
# 释放锁
lock.release()
if balance != 100:
# 如果输出这句话,说明线程不安全
print("balance=%d" % balance)
break
if __name__ == "__main__":
thr1 = threading.Thread(target=change,args=(100,500000),name='t1')
thr2 = threading.Thread(target=change,args=(100,500000),name='t2')
thr1.start()
thr2.start()
thr1.join()
thr2.join()
print("{0} 线程结束".format(threading.current_thread().getName()))
运行结果:
MainThread 线程结束