并发编程 线程
一、什么是线程?
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
线程发展:
60年代,在OS中能拥有资源和独立运行的基本单位是进程,然而随着计算机技术的发展,进程出现了很多弊端,一是由于进程是资源拥有者,创建、撤消与切换存在较大的时空开销,因此需要引入轻型进程;二是由于对称多处理机(SMP)出现,可以满足多个运行单位,而多个进程并行开销过大。
因此在80年代,出现了能独立运行的基本单位——线程(Threads)。
二、进程与线程的关系
-
地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
-
通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
-
调度和切换:线程上下文切换比进程上下文切换要快得多。
-
在多线程操作系统中,进程不是一个可执行的实体。
三、线程的特点
-
多线程共享一个进程的地址空间
-
线程比进程更轻量级,线程比进程更容易创建可撤销,在许多操作系统中,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用
-
若多个线程都是cpu密集型的,那么并不能获得性能上的增强,但是如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度。
-
在多cpu系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销要小的多。(这一条并不适用于python)
四、python模块threading
在python中使用模块threading操作线程,threading模块和multiprocess用法基本一致。
创建线程的两种方式
方式一:
from threading import Thread
import time
def test(name):
time.sleep(2)
print('%s say hello' %name)
if __name__ == '__main__':
t=Thread(target=test,args=('xie',))
t.start()
print('主线程')
方式二:
from threading import Thread
import time
class MyThread(Thread):
def __init__(self,name):
super().__init__()
self.name=name
def run(self):
time.sleep(2)
print('%s say hello' % self.name)
if __name__ == '__main__':
t = MyThread('xie')
t.start()
print('主线程')
同进程中线程之间数据共享
from threading import Thread
def func():
global n
n=0
if __name__ == 'main':
n=1
t=Thread(target=func)
t.start()
t.join()
print('主',n) # 主 0
线程模块中的其他方法
# t.isAlive(): 返回线程是否活动的
# t.getName(): 返回线程名
# t.setName(): 设置线程名
# threading.currentThread(): 返回当前的线程变量
# threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程
# threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果
守护线程
随主线程运行完毕而停止并被回收。对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕。
from threading import Thread
import time
def test(name):
time.sleep(2)
print('%s say hello' %name)
if __name__ == '__main__':
t=Thread(target=test,args=('xie',))
t.daemon = True # 必须在t.start()之前设置
t.start()
print('主线程')
print(t.is_alive())
'''
主线程
True
'''
from threading import Thread
import time
def foo():
print(123)
time.sleep(1)
print("end123")
def bar():
print(456)
time.sleep(3)
print("end456")
t1=Thread(target=foo)
t2=Thread(target=bar)
t1.daemon=True
t1.start()
t2.start()
print("main-------")
# 输出依次:
# 123
# 456
# main-------
# end123
# end456
互斥锁
模拟100个人同时抢票的情况,在不加锁的情况下数据是不安全的,就很有可能造成最后的数据与逻辑不符。
from threading import Thread
import time
def grab():
global n
temp=n
time.sleep(0.1)
n=temp-1
if __name__ == '__main__':
n=100
l=[]
for i in range(100):
t=Thread(target=grab)
l.append(p)
t.start()
for t in l:
t.join()
print(n) #结果可能为99
我们需要加锁保证数据的安全,即把并行改成串行,保证同一时间内只有一个线程执行对数据的操作。threading与multiprocess类似,也有Lock。
from threading import Thread,Lock
import time
def grab():
global n
lock.acquire()
temp=n
time.sleep(0.1)
n=temp-1
lock.release()
if __name__ == '__main__':
lock=Lock()
n=100
l=[]
for i in range(100):
t=Thread(target=grab)
l.append(t)
t.start()
for t in l:
t.join()
print(n) # 结果肯定为0,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全