1. GIL全局解释器锁

Python在设计之处就考虑到要在主循环中, 同时只有一个线程在执行.
虽然Python解释器中可以运行多个线程, 但在任意时刻只有一个线程在解释器中运行.

GIL锁控制在同一个时刻只有一个线程在运行.

* Python中的多线程是假的多线程, 即使100个线程跑在100核CPU上, 也只能用到1个核.

2. 线程

为什么会出现线程?
进程的缺点:
    1. 非常消耗资源,计算机不能无限开启子进程.
    2. 如果开了过多的进程,cpu的切换进程的模式下是非常耗时的.

因为进程的缺点,线程的出现就是为了解决进程的缺点,线程的开销小于进程.

线程: 是轻量级的进程, 一个进程里面至少有一个线程, 线程就是具体干活的, 执行任务的.
线程比进程更加容易创建和撤销, 创建一个线程比创建一个进程要快10-100, 
多个线程的运行也是在多个线程之间的快速切换, 一个线程可以访问另一个线程的内存地址.

PS:进程相当于一座工厂,线程相当于干活的人
PS:进程是一个资源的实体单位,而cpu操作的最小单位是线程.

2. 开启多线程

开启线程是使用在进程申请的内存空间.

2.1 模块

multiprocessing模块完全模仿了 threading模块的接口, 线程与进程的使用大致相似.
2.2 Thread类
Thread()是创建线程的类, 由该类实例化得到线程对象.
def __init__(self, group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None):

参数介绍:
target:  表示调用对象, 子线程要执行的任务.
args:    表示调用对象的位置参数(元组).
kwargs:  表示调用对象的关键字参数(字典).
name:    表示子进程的名称.
2.3 属性
.setDaemon    默认值为False,设置为True, 设置子线程为守护线程, 
	         父线进程结束, 子线程也结束, 需要在.start前设置.
.is_alive()   查看进程是否存活
.getName()    查看进程名称
.setName()    设置进程名称
2.4 绑定方法
.start()      启动线程, 内部调用线程对象的.run()方法.
.run()        进程启动时运行的方法, 正是它去调用target指定的函数, 自定义的内中必须实现该方法.
.terminate()  强制终止线程, 不会进行任何清理.
.isalive()    查看线程是否存活, 返回值为布尔值.
.join()       主进程等待线程的结束.

3. 开启线程的两种方式

线程可以不用在 if __name__ == '__mian__': 下执行.
3.1 方式1
* 1. 定义子线程调用的函数
* 2. 创建子线程对象, 绑定调用的函数.
* 3. 启动子线程
from threading import Thread


# 子线程调用的函数
def task():
    print('我是线程')


# 创建子线程对象
t = Thread(target=task)
t.start()

print('我是主进程')

终端显示:
我是线程
我是主线程
_________________________________________________________________________
可能显现在一行: 我是线程我是主线程 因为线程执行速度快, 两个代码抢占终端的使用.
3.2方式2
* 1. 继承Thread类, 自定义线程类, 调用父类的初始化方法, 生成对象.
* 2. 将线程需要执行的代码写在run()方法中.
* 3. 调用自定义类, 创建子线程对象.
* 4. 启动子线程.
from threading import Thread

class Task(Thread):
    def __init__(self):
        super().__init__()

    def run(self):
        print('我是进程')

t = Task()
t.start()
print('我是主线程')
终端显示:
我是线程
我是主线程
3.3 速度比较
线程与进程的速度比较.
# 线程
from threading import Thread
import time


# 定义线程调用的函数
def task():
    print('我是线程')


# 时间戳
ctime = time.time()
# 创建线程对象
t = Thread(target=task)
# 启动线程
t.start()
# 等待子进程运行结束再往后运行
t.join()
print('此次运行时间%0.8s秒' % (time.time() - ctime))

终端显示: (皮秒级别)
我是线程
此次运行时间0.000633
# 进程
from multiprocessing import Process
import time


# 子线程调用的函数
def task():
    print('我是进程')


if __name__ == '__main__':
    # 获取时间戳
    ctime = time.time()
    # 创建子进程对象
    p = Process(target=task)
    # 启动子进程
    p.start()
    # 等待子进程运行结束再往后运行
    p.join()
    print('此次运行时间%0.8s秒' % (time.time() - ctime))

终端显示: (微秒级别)
我是子进程
此次运行时间0.061532
对象结果: 创建到销毁, 线程比进程要快100.

4. 线程之间数据共享

4.1 获取pid
在主进程下开启多个线程, 每个线程中获取pid进程标识符与主进程一样.
from threading import Thread
import os


# 线程1调用的函数
def task1():
    print(f'线程1, 获取pid为{os.getpid()}')


# 线程2调用的函数
def task2():
    print(f'线程2, 获取pid为{os.getpid()}')


# 线程调用函数列表
task_list = [task1, task2]
# 创建线程对象
for task in thread_list:
    t = Thread(target=task)
    t.start()

print(f'主进程, 获取pid为{os.getpid()}')

终端显示:
线程1, 获取pid为16716
线程2, 获取pid为16716
主进程, 获取pid为16716
4.2 线程中修改数据
from threading import Thread

# 在全局名称空间中定义一个变量
x = 0


# 定义线程中调用的函数
def task():
    global x
    x = 10


t = Thread(target=task)
t.start()
# 等待线程执行完毕
t.join()
print(x)

终端显示:
10
_______________________________
所有线程与主进程, 共用一个名称空间.

5. 守护线程

.setDaemon(True), 主进程执行完毕, 立刻销毁子进程..start()前设置.
from threading import Thread
import time


# 线程调用的函数
def task():
    print(1111)
    # 延时3秒
    time.sleep(3)
    print(3333)


# 创基线程对象
t = Thread(target=task)
# 设置守护进程
t.setDaemon(True)
# 启动线程
t.start()
print(2222)

终端显示:
1111
2222
_________________________
当主进行结束, 线程马上停止.

6. 互斥锁

并发速度太快了, 同时使用共享资源会出问题.
6.1 模拟抢票(不上锁)
在项目目录下新建 db.txt, 并写入数据:
{"count": 1}
from threading import Thread
import time, json


# 查票
def search(i):
    ticket = json.load(open('db.txt'))
    print('%s查看车票剩余:%s张!' % (i, ticket['count']))
    return ticket


# 抢票
def rob(ticket, i):
    if ticket['count'] > 0:
        ticket['count'] -= 1
        time.sleep(0.2)  # 网络延时
        json.dump(ticket, open('db.txt', 'w'))
        print('%s抢票成功' % i)
    else:
        print('%s没有抢到票' % i)


# 合并到一个函数中
def func(i):
    ticket = search(i)
    rob(ticket, i)


if __name__ == '__main__':
    # 生成5个子进程模拟抢票
    for i in range(5):
        t = Thread(target=func, args=(i,))
        t.start()
终端显示:	
0查看车票剩余:1张!
1查看车票剩余:1张!
2查看车票剩余:1张!
4查看车票剩余:1张!
3查看车票剩余:1张!

3抢票成功
4抢票成功
1抢票成功
2抢票成功
0抢票成功
______________________________
一张车票所有子线程都抢到了...
6.2 模拟抢票(上锁)
Python的GIL锁保证同一个时刻只有一个线程在执行, 锁的目的是为了保护共享的数据, 
同一个时间只有一个线程来修改数据, 保护不同的数据因该加上不同的锁.
GIL  Lock 是两把不同的锁, 保护不一样的数据, 比如垃圾回收, 
后者是保护自己开发的应用程序的数据, GIL锁不负责这个事, 只能自定义加锁处理.

GIL是解释器级别的, 保护解释器级别的数据, 所有线程抢GIL锁, 抢的就是执行的权限.
在线程中使用Lock互斥锁, lock_obj.acquire() 为上锁, lock_obj.release() 释放锁.
from threading import Thread, Lock
import time, json


# 查票
def search(i):
    ticket = json.load(open('db.txt'))
    print('%s查看车票剩余:%s张!' % (i, ticket['count']))
    return ticket


# 抢票
def rob(ticket, i):
    if ticket['count'] > 0:
        ticket['count'] -= 1
        time.sleep(0.2)  # 网络延时
        json.dump(ticket, open('db.txt', 'w'))
        print('%s抢票成功' % i)
    else:
        print('%s没有抢到票' % i)


# 合并到一个函数中
def func(i, lock):
    # 使用锁
    mutex .acquire()
    ticket = search(i)

    rob(ticket, i)
    # 释放锁
    mutex .release()


if __name__ == '__main__':
    # 生成锁对象
    mutex  = Lock()
    # 生成5个子进程模拟抢票
    for i in range(5):
        t = Thread(target=func, args=(i, mutex ))
        t.start()

7. 信号量

import time
from threading import Thread, Semaphore


def task(i, locks):
    # 加信号量锁
    locks.acquire()
    print('%s号程序开始执行!' % i)
    time.sleep(2)
    print('%s号程序执行完毕!' % i)
    time.sleep(2)
    # 释放信号量锁
    locks.release()


# 生成两把锁
locks = Semaphore(2)
for i in range(10):
    # 开启10个线程, 每次两个线程抢锁执行
    t = Thread(target=task, args=(i, locks))
    t.start()

终端显示:
0号程序开始执行!
1号程序开始执行!
1号程序执行完毕!
0号程序执行完毕!
2号程序开始执行!3号程序开始执行!

2号程序执行完毕!3号程序执行完毕!

4号程序开始执行!
5号程序开始执行!
5号程序执行完毕!4号程序执行完毕!

6号程序开始执行!
7号程序开始执行!
6号程序执行完毕!
7号程序执行完毕!
8号程序开始执行!9号程序开始执行!

8号程序执行完毕!
9号程序执行完毕!

8. Event事件

import time
from threading import Thread, Event, Lock

num = 0


def task1(event):
    print('%s 开始准备发红包' % 'kid')
    time.sleep(1)
    # 红包数量
    global num
    num = 1
    print('%s 已经发红包了' % 'kid')
    event.set()


def task2(i, event, lock):
    print(f'{i}号同学掏出来手机, 准备抢红包!')
    event.wait()
    lock.acquire()
    # 抢了红包就没了
    global num
    # 查询红包数量
    tem = num
    # 还有红包就抢
    if num > 0:
        num -= 1
        print(f'现在还有{tem}个红包, {i} 同学抢到了红包')
    else:
        print(f'现在还有{tem}个红包, {i} 同学手速慢了!')
    lock.release()


event = Event()
t0 = Thread(target=task1, args=(event,))
t0.start()

#  生成一把互斥锁
lock = Lock()
for i in range(5):
    t1 = Thread(target=task2, args=(i, event, lock))
    t1.start()

终端显示:
kid 开始准备发红包
0号同学掏出来手机, 准备抢红包!
1号同学掏出来手机, 准备抢红包!
2号同学掏出来手机, 准备抢红包!
3号同学掏出来手机, 准备抢红包!
4号同学掏出来手机, 准备抢红包!
kid 已经发红包了
现在还有1个红包, 0 同学抢到了红包
现在还有0个红包, 1 同学手速慢了!
现在还有0个红包, 3 同学手速慢了!
现在还有0个红包, 2 同学手速慢了!
现在还有0个红包, 4 同学手速慢了!
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐