目录
十一、GIL全局解释锁 global interpreter lock
一、线程的概念
进程是资源的拥有者
进程是资源分配和调度的最小单位
线程是CPU调度的最小单位(程序真正执行的时候运行的是线程)
每个进程至少有一个线程
二、进程和线程之间的关系
三、使用threading模块创建线程
1.import threading
threading.Thread(target=函数名,arge=(参数1,参数2......))
target | 指向执行的对象 |
arge | 参数,保存在元组中 |
等子线程结束后,主线程结束,程序结束了
2.传递参数:
3.join()方法
当前线程执行完后,其他线程才会继续执行
import time
import threading
def sing():
for i in range(3):
print('正在唱歌...', i)
time.sleep(1)
def dance():
for i in range(3):
print('正在跳舞...', i)
time.sleep(1)
def main():
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t1.join() # 效果:t1执行完后,t2和主线程同时执行
t2.start()
# t1.join() # 效果:t1,t2子进程执行,当t1,t2结束了,主线程才执行
# t2.join()
if __name__ == '__main__':
main()
print('程序结束了...')
4.setDeamon()
将当前线程设置成守护线程,守护主线程
当主线程结束后,守护线程也结束了,不管是否执行完
import time
import threading
def sing():
for i in range(3):
print('正在唱歌...', i)
time.sleep(1)
def dance():
for i in range(3):
print('正在跳舞...', i)
time.sleep(1)
def main():
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
# 把t1,t2都设置成守护线程,主线程一死,t1,t2都要死,不管他们有没有执行完
# r如果只把其中一个设置成守护线程,一个会把另一个给救了,程序正常执行
t1.setDaemon(True)
t2.setDaemon(True)
t1.start()
t2.start()
if __name__ == '__main__':
main()
print('程序结束了...')
# 正在唱歌... 0
# 正在跳舞... 0
# 程序结束了...
四、实例方法
getName() | 获取线程的名字 |
setName() | 设置线程的名字 |
is_alive() | 判断线程是否活着 |
'''
实例方法:
getName()
setName()
is_alive():判断线程是否活着
'''
import time
import threading
def sing(): # t1子线程执行的开始
for i in range(3):
print('正在唱歌...', i)
time.sleep(1)
def dance(): # t2子线程执行的开始
for i in range(3):
print('正在跳舞...', i)
time.sleep(1)
def main():
t1 = threading.Thread(target=sing) # 创建t1子线程,执行sing函数
t2 = threading.Thread(target=dance)
print(t1.is_alive()) # False 用is_alive() 下划线命名法,比较规范
print(t2.is_alive()) # False
t1.start() # 开启t1子线程
print(t1.is_alive()) # True
t2.start()
# 给线程设置名称
t1.setName('张飞')
t2.setName('关羽')
# 获取线程的名称
print(t1.getName())
print(t2.getName())
if __name__ == '__main__':
main() # 主线程执行的开始
print('程序结束了...')
# False
# False
# 正在唱歌... 0
# True
# 正在跳舞... 0
# 张飞
# 关羽
# 程序结束了...
# 正在跳舞... 1
# 正在唱歌... 1
# 正在跳舞... 2
# 正在唱歌... 2
五、threading模块提供的方法
current_thread() | 返回当前的线程对象 |
enumerate() | 返回正在运行的线程,存放在一个列表中 |
active_count() | 返回现在活着的线程的数量 |
import time
import threading
def sing():
for i in range(3):
print('正在唱歌...', i)
time.sleep(1)
print(threading.current_thread()) # <Thread(Thread-2, started 6868)>
def dance():
for i in range(3):
print('正在跳舞...', 3)
time.sleep(1)
print(threading.current_thread()) # <Thread(Thread-1, started 4704)>
def main():
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
print(threading.current_thread()) # <_MainThread(MainThread, started 6484)>
print(threading.enumerate())
# [<_MainThread(MainThread, started 6484)>, <Thread(Thread-1, started 4704)>, <Thread(Thread-2, started 6868)>]
print(threading.active_count()) # 3
if __name__ == '__main__':
main()
print('程序结束了...')
六、使用继承的方式开启线程
1.继承threading.Thread
复写父类的run()方法
2.传递参数
七、多线程共享全局变量
1.多线程共享全局变量
2.共享全局变量的问题
两个线程的操作 | 会不会出现问题 |
全写 | 有问题 |
一个读,一个写 | 可能会有问题 |
全读 | 没问题 |
只要涉及到写,就可能会有问题
'''
共享变量的问题:
当传进去100的时候,没问题,100 200
当传进去1000000的时候,出现问题, 并且每次结果都不一样 数字变少了
写100的时候,消耗的代码相当少
一个读一个写
两个都写
多个线程同时操作全局变量,就会有问题
两个都读的话就没问题
只要涉及到写的话,就有可能有问题
想要解决这个问题的话:
'''
# 一个读一个写
import threading
g_num = 0
def test1(num):
global g_num
for i in range(num):
g_num += 1
print('test1--->', g_num)
def test2(num):
global g_num
for i in range(num):
g_num += 1
print('test2-->', g_num)
if __name__ == '__main__':
t1 = threading.Thread(target=test1, args=(1000000,))
t2 = threading.Thread(target=test2, args=(1000000,))
t1.start()
t2.start()
'''
1.先获取g_num的值
2.g_num+1
3.将g_num的值保存在g_num中
t1获取到cpu,g_num=1
此时,t2获取到cpu,
'''
import threading
g_num = 0
def test1(num):
global g_num
for i in range(num):
g_num += 1
print('test1--->', g_num)
def test2(num):
print('test2-->', g_num)
if __name__ == '__main__':
t1 = threading.Thread(target=test1, args=(1000000,))
t2 = threading.Thread(target=test2, args=(1000000,))
t1.start()
t2.start()
# test2--> 235981
# test1---> 1000000
3.解决共享全局变量的方法:互斥锁
(1)同步,异步
同步:合作
同步是指协同、协助、互相配合。
A强依赖B(对方),A必须等到B的回复,才能做出下一步响应。即A的操作(行程)是顺序执行的,中间少了哪一步都不可以,或者说中间哪一步出错都不可以。
异步:
异步则相反,A并不强依赖B,A对B响应的时间也不敏感,无论B返回还是不返回,A都能继续运行;B响应并返回了,A就继续做之前的事情,B没有响应,A就做其他的事情。也就是说A不存在等待对方的概念。
(2)互斥锁
当多个线程几乎同时修改一个共享数据的时候,需要进行同步控制,线程同步能够保证多个线程安全的访问竞争资源(全局内容),最简单的同步机制就是使用互斥锁。
八、死锁
1.死锁产生的原因
在多个线程共享资源的时候,如果两个线程分别占有一部分资源,并且同时等待对方的资源,就会造成死锁现象。
2.注意
如果锁之间相互嵌套,就有可能出现死锁。因此尽量不要出现锁之间的嵌套。
九、线程队列
put() | 往队列里面放数据 |
put_nowait() | 往队列里面放数据,当队列满的时候,报错 |
get() | 从队列中取数据 |
get_nowait() | 从队列中取数据,当队列空的时候,报错 |
qsize() | 返回队列的长度 |
empty() | 判断队列是否为空 |
full() | 判断队列时候满 |
task_done() | 发出(队列为空)信号 |
join() | 接收(队列为空)信号 |
task_done():任务结束 发出信号
join():接收信号
import queue
q = queue.Queue()
q.put('hahaha...')
print(q.get())
q.task_done()
q.join()
print('---->>>')
十、生产者与消费者模型
1.PV操作
semaphore mutex=1;#临界区的互斥信号量
semaphore empty=n;#空闲缓存区
semaphore full=0;#缓存区初始化为空
producer()#生产者进程
{
while(1)
{
produce an item in nextp;#生产数量
P(empty);#获取空缓冲区
p(mutex);#进入临界区
add nextp to buffer;#将数据放入缓冲区
v(mutex)#离开缓冲区
v(full)#满缓冲区数加1
}
}
consumer()#消费者进程
{
while(1)
{
p(full);#获取满缓冲区单元
p(mutex);#进入临界区
remove an item from buffer;#从缓冲区中取出数据
v(mutex);#离开缓冲区
v(empty);#空缓冲区数加1
consume the item;#消费数据
}
}
2.用python的队列实现
import queue, threading
q = queue.Queue()
lock = threading.Lock()
def producer(name):
count = 1
while count <= 100:
q.join()
lock.acquire()
q.put(count)
print('{}正在做第{}碗面条'.format(name, count))
count+=1
lock.release()
def consumer(name):
count = 1
while count <= 100:
q.get()
lock.acquire()
print('{}正在做第{}碗面条'.format(name, count))
count+=1
lock.release()
q.task_done()
def main():
t1 = threading.Thread(target=producer, args=('海参',))
t2 = threading.Thread(target=consumer, args=('小宝',))
t1.start()
t2.start()
if __name__ == '__main__':
main()
十一、GIL全局解释锁 global interpreter lock
1.并行,并发
(1)并行
多个CPU同时执行多个任务,就好像有两个程序,这两个程序是真的在两个不同的CPU内同时被执行。
(2)并发
CPU交替处理多个任务,还是有两个程序,但是只有一个CPU,会交替处理这两个程序,而不是同时执行,只不过因为CPU执行的速度过快,而会使得人们感到是在“同时”执行,执行的先后取决于各个程序对于时间片资源的争夺.
并行和并发同属于多任务,目的是要提高CPU的使用效率。这里需要注意的是,一个CPU永远不可能实现并行,即一个CPU不能同时运行多个程序。
2.GIL
Guido van Rossum(吉多·范罗苏姆)创建python时就只考虑到单核cpu,解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁, 于是有了GIL这把超级大锁。因为cpython解析只允许拥有GIL全局解析器锁才能运行程序,这样就保证了保证同一个时刻只允许一个线程可以使用cpu。也就是说多线程并不是真正意义上的同时执行。
小彩蛋 :如何下载包?
(1)pycharm
setting-->Project-->Project Interpreter--> + ----->在搜索框中输入自己需要的包-->Install Package
import pygame
pygame.init()
pygame.display.set_mode((320, 568))
while True:
pass
pygame.quit()
- 运行结果:
- 控制台的信息: