首页 > 技术文章 > 线程

Deaseyy 2019-05-13 23:30 原文

 

 GIL:全局解释器锁,
#     作用:只允许一个线程通过,所有Python中的多线程是假的
#   进程:系统分配的一个资源单位
#   线程:进程中的一个分支,进程中至少有一个主线程
#   多线程: 多个线程并发的一种技术
#   同步:按顺序执行
#   异步:可以理解为在不同的线程中独立执行
#   并行:任务数 <= CPU数
#   并发: 任务数 > CPU数
#
#   线程锁/互斥锁: threading.Lock,解决线程冲突
#   死锁:多个线程同一时刻占用资源不愿意释放,又想占用对方的资源,造成相互等待的现象
#   递归锁:threading.RLock, 解决死锁
#
#   信号量:控制线程的最大并发数

 

线程

在一个进程的内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”叫做线程。

线程通常叫做轻型的进程。线程是共享内存空间的并发执行的多任务,每一个线程都共享一个进程的资源。

线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。

模块 1、_thread模块 低级模块,接近底层。 2、threading模块 高级模块,对_thread进行了封装

线程基本用法

基本跟进程类似。

from threading import Thread, current_thread
import time

def run(num):
   print('子线程(%s)开始' % (current_thread().name,))
   time.sleep(2)
   print('打印', num)
   time.sleep(2)
   print('子线程(%s)结束' % (current_thread().name,))


if __name__ == '__main__':
   # 任何进程默认就会启动一个线程,称为主线程,主线程可以启动新的子线程
   # current_thread():返回返回当前线程的实例
   print('主线程(%s)开始' % (current_thread().name,))
   # 创建子线程
   t = Thread(target=run, args=(1,), name='runThread')
   t.start()
   # 等待线程结束
   t.join()
   print("主线程(%s)结束" % (current_thread().name))

练习: 将上述新建线程的代码改成使用继承Thread类的方式。

常见方法

方法名说明
isAlive() 返回线程是否在运行。正在运行指启动后、终止前。
get/setName(name) 获取/设置线程名。
start() 线程准备就绪,等待CPU调度
is/setDaemon(bool) 获取/设置是守护线程(默认前台线程(False))。(在start之前设置)
join([timeout]) 阻塞当前上下文环境的线程,直到调用此方法的线程终止或到达指定的timeout(可选参数)

线程间共享数据

线程数据是共享的。先来看一个简单的例子。

from threading import Thread
from time import sleep


# 全局数据
num = 100


def run():
   print('子线程开始')
   global  num
   num += 1
   print('子线程结束')


if __name__ == '__main__':
   print('主线程开始')
   # 创建主线程
   t = Thread(target=run)
   t.start()
   t.join()
   print(num)
   print('主线程结束')

执行结果:

主线程开始
子线程开始
子线程结束
101
主线程结束

可以看到子线程修改的全局变量,在主线程中也体现出来了。

线程之间数据共享引发的问题

多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在每个进程中,互不影响。而多线程中,所有变量都由所有线程共享。所以,任何一个变量都可以被任意一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时修改一个变量,容易把内容改乱了。

下面来模拟这个问题:

from threading import Thread


# 全局变量
num = 100

def run(n):
   global num
   for i in range(100000000):
       num = num + n
       num = num - n


if __name__ == '__main__':
   t1 = Thread(target=run, args=(6,))
   t2 = Thread(target=run, args=(9,))
   t1.start()
   t2.start()
   t1.join()
   t2.join()
   print('num=',num)

运行之后发现num不等于100了。这就是因为两个线程都对num进行操作,中间发生了紊乱。

使用线程锁解决数据混乱问题

threading中的Lock类表示锁。

锁确保了这段代码只能由一个线程从头到尾的完整执行。阻止了多线程的并发执行,包含锁的某段代码实际上只能以单线程模式执行,所以效率大大滴降低了。

由于可以存在多个锁,不同线程持有不同的锁,并试图获取其他的锁,可能造成死锁,导致多个线程挂起。只能靠操作系统强制终止。

我们给之前的代码加上锁 来解决数据混乱的问题。

from threading import Thread, Lock


# 全局变量
num = 100
# 锁对象
lock = Lock()

def run(n):
   global num
   global lock
   for i in range(100000000):
       # 获取锁
       lock.acquire()
       try:
           num = num + n
           num = num - n
       finally:
           # 修改完一定 要释放锁
           lock.release()


if __name__ == '__main__':
   t1 = Thread(target=run, args=(6,))
   t2 = Thread(target=run, args=(9,))
   t1.start()
   t2.start()
   t1.join()
   t2.join()
   print('num=',num)

使用上下文管理器 with,可以自动获取锁,释放锁。可以将上面代码的try语句改成如下:

with lock:
num = num + n
num = num - n

 

推荐阅读