Python进阶丨进程和线程 —— 多线程

多线程

- 描述

多线程类似于同时执行多个不同程序

- 优点

a.使用线程可以把占据长时间的程序中的任务放到后台去处理
b.用户界面可以更加吸引人,比如用户点击一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度
c.程序的运行速度可能加快
d.在一些等待的任务实现上如用户输入。文件读写和网络收发数据等,线程就比较有用。

- 线程种类

内核线程:由操作系统内核创建和撤销

用户线程:不需要内核支持而在程序中实现的线程

- 线程模块

_thread:低级模块

threading:高级模块(推荐使用)

- threading方法

run():用以表示线程活动的方法

start():启动线程活动

join([time]):等待至线程中止。这堵塞调用线程直至线程的join()方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生

isAlive():返回线程是否活动的

getName():返回线程名

setName():设置线程名

import time
import threading

# 新线程执行的代码
def loop():
    print('thread {} is running...'.format(threading.current_thread().name))
    n = 0
    while n < 5:
        n = n + 1
        print('thread {} >>> {}'.format(threading.current_thread().name, n))
        time.sleep(1)
    print('thread {} ended'.format(threading.current_thread().name))

print('thread {} is running...'.format(threading.current_thread().name))

t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('thread {} ended.'.format(threading.current_thread().name))

输出结果

thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended
thread MainThread ended.

由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程。

Python的threading模块有个current_thread()函数,他永远返回当前线程的实例。

主线程实例的名字叫MainThread,子线程的名字在创建时指定,用LoopThread命名子线程。名字仅仅在打印时用来显示,没有其他意义。

- 线程同步

多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享。

为了保证数据的正确性,需要对多个线程进行同步

多线程该乱变量案例

import time
import threading

# 假定银行存款
balance = 0

def change_it(n):
    # 先存后取,结果应该为0
    global balance
    balance = balance + n 
    balance = balance - n 

def run_thread(n):
    for i in range(100000):
        change_it(n)

t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)

共享变量balance,初始值为0,启动两个线程,先存后取,理论结果应该为0,但由于线程的调度是由操作系统决定的,当t1、t2交替执行时,只要循环次数足够多,balance的结果不一定为0

高级语言的一条语句在CPU执行时时若干条语句

balance = balance + n 

分为两部
1.计算balance + n ,存入临时变量中
2.将临时变量的值赋给balance

相当于

x = balance + n
balance = x

由于x是局部变量,两个线程各自都有自己的x

代码正常执行时

初始值 balance = 0

t1 : x1 = balance + 5  # x1 = 0 + 5 = 5
t1 : balance = x1      # balance = 5
t1 : x1 = balance - 5  # x1 = 5 - 5 = 9
t1 : balance = x1      # balance = 0

t2 : x2 = banlance + 8 
t2 : balance = x2
t2 : x2 = balance - 8
t2 : balance = x2

结果:balance = 0

但t1和t2诗交替运行的,如果操作系统以下面的顺序执行t1、t2

初始值 balance = 0

t1 : x1 = balance + 5  # x1 = 0 + 5 = 5

t2 : x2 = balance + 8  # x2 = 0 + 8 = 8
t2 : balance = x2      # balance = 8

t1 : balance = x1      # balance = 5
t1 : x1 = balance - 5  # x1 = 5 - 5 = 0
t1 : balance = x1      # balance = 0

t2 : t2 = balance - 8  # x2 = 0 - 8 = -8
t2 : banlance = x2     # balance = -8

结果:balance = -8

因为修改balance需要多条语句,而执行官这条语句时,线程可能中断,从而导致多个线程把一同一个对象的内容改乱了。

为了确保balance计算正确,需要给change_it()上一把锁,当线程开始执行change_it()时,该线程获得了锁,其他线程便不能同时执行change_it(),只能等待,直到锁被释放后,获得该锁以后才能修改。

通过threading.Lock()来创建锁

balance = 0
lock = threading.Lock()

def run_thread(n):
    for i in range(1000000):
        # 先要获取锁
        lock.acquire()
        try:
            # 放心地修改吧
            change_it(n)
        finally:
            # 改完了一定要释放锁
            lock.release()

当多个线程同时执行lock.acquire()时,只有一个线程能成功获取锁,然后继续执行代码,其他线程就继续等待直到获取锁为止。

获得锁的线程用完后一定要释放锁,否则等待锁的线程将永远等待下去,成为死线程。用try...finally来确保锁一定会被释放。

- GIL全局锁(Global Interpreter Lock)

任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。

由于GIL全局锁的存在,多线程在Python中只能交替执行,在多核CPU上,也只能用到1个核

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值