线程
进程是
计算机中最小的资源分配单位
进程对于操作系统来说还是有一定负担
创建一个进程 操作系统要分配的资源大致有 :
代码
数据
文件
为什么要有线程
轻量级的概念
他没有属于自己的进程资源:
一条线程只负责执行代码,没有自己独立的
代码、变量、文件资源
什么是线程
线程是计算机中被CPU调度的最小单位
你的计算机当中的cpu都是执行的线程中的代码
线程和进程之间的关系
每一个进程中都有至少一条线程在工作
线程的特点
同一个进程中的所有线程的资源是共享的
轻量级 没有自己的资源
进程和线程之间的区别
占用的资源
调度的效率
资源是否共享
通用的问题
在java c++ c# 中一个进程中的多个线程能够并行
- python中的线程
一个进程中的多个线程不能够并行
python是一个解释型语言
Cpython解释器 内部有一把全局解释器锁 GIL
所以线程不能充分的利用多核
同一时刻用一个进程中的线程只有一个能被CPU执行
GIL锁 确实是限制了你的程序效率
GIL锁 目前 是能够帮助你在线程的切换中提高效率
就是想写高计算型:
多进程
换一个解释器
不同的进程之间是充满敌意的,彼此是抢占、竞争cpu的关系,如果迅雷会和QQ抢资源。而同一个进程是由一个程序员的程序创建,所以同一进程内的线程是合作关系,
thread模块提供了基本的线程和锁的支持,threading提供了更高级别、功能更强的线程管理的功能,threading模块更为先进,对线程的支持更为完善.thread模块不支持守护线程
multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性
- 并发
import os
import time
from threading import Thread
from multiprocessing import Process
def func(i):
print('子 :',i,os.getpid()) # 子线程进程pid 和父线程pid相同因为同属于同一个进程空间
print('主 :',os.getpid())
for i in range(10):
t = Thread(target=func,args=(i,))
t.start()
类的形式创建线程
from threading import Thread
import time
class Sayhi(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 = Sayhi('egon')
t.start()
print('主线程')
- 进程和线程的速度比较
def func(i):
print('子 :',i,os.getpid())
if __name__ == '__main__':
start = time.time()
t_lst = []
for i in range(100):
t = Thread(target=func,args=(i,))
t.start()
t_lst.append(t)
for t in t_lst:t.join()
tt = time.time()-start
start = time.time()
t_lst = []
for i in range(100):
t = Process(target=func, args=(i,))
t.start()
t_lst.append(t)
for t in t_lst: t.join()
pt = time.time() - start
print(tt,pt)
- 线程数据共享(同内存地址空间)
from threading import Thread
num = 100
def func():
global num
num -= 1
t_lst = []
for i in range(100):
t = Thread(target=func)
t.start()
t_lst.append(t)
for t in t_lst:t.join()
print(num)
Thread实例对象的方法
isAlive(): 返回线程是否活动的。
getName(): 返回线程名。
setName(): 设置线程名。
threading模块提供的一些方法:
threading.currentThread(): 返回当前的线程变量。
threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果
- 主线程等待子线程结束
from threading import Thread
import time
def sayhi(name):
time.sleep(2)
print('%s say hello' %name)
if __name__ == '__main__':
t=Thread(target=sayhi,args=('egon',))
t.start()
# t.join() # 阻塞等待
print('主线程')
print(t.is_alive())
- 守护进程
1.对主进程来说,运行完毕指的是主进程代码运行完毕
2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
详细解释:
1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束,
2 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
守护线程 是在主线程代码结束之后,还等待了非守护子线程执行结束才结束
主线程结束 就意味着主进程结束
主线程等待所有的线程结束
主线程结束了之后 守护线程随着主进程的结束自然结束了
import time
from threading import Thread
def func1():
while True:
time.sleep(0.5)
print(123)
def func2():
print('func2 start')
time.sleep(3)
print('func2 end')
t1 = Thread(target=func1)
t2 = Thread(target=func2)
t1.setDaemon(True)
t1.start()
t2.start()
print('主线程的代码结束') # 主线程代码正常运行
协程
协程 :纤程
一条线程 在多个任务之间来回切换
切换这个动作是浪费时间的
对于CPU、操作系统来说 协程是不存在的
他们只能看到线程
进程 :计算机中最小的资源分配单位
线程 :计算机中能被cpu执行的最小单位
并发的本质:切换+保存状态
协程:是单线程下的并发,又称微线程
协程是一种用户态的轻量级线程,即协程是由用 户程序自己控制调度的。
协程的本质就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率。
- python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
- 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)
- 协程在程序之间的切换操作系统感知不到,无论开启多少个协程对操作系统来说总是一个线程
- 协程的本质就是一条线程,所以完全不会产生数据安全的问题(python代码作切换,而非cpu指令)
- (*****) nginx模块:消息的接受和转发
协程模块:
- greenlet:
gevent的底层,协程切换的模块,只切换不能识别IO
单纯的切换(在没有io的情况下或者没有重复开辟内存空间的操作),反而会降低程序的执行速度
import time
from greenlet import greenlet
def eat(name):
print('%s eat 1' %name)
g2.switch('egon')
print('%s eat 2' %name)
g2.switch()
def play(name):
print('%s play 1' %name)
g1.switch()
print('%s play 2' %name)
g1=greenlet(eat)
g2=greenlet(play)
g1.switch('egon')#可以在第一次switch时传入参数,以后都不需要
- gevent :
gevent能提供更全面的功能
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
自动的检测阻塞事件,遇见阻塞了就会进行切换,有些阻塞它不认识
import time
import gevent
def eat():
print('eating 1')
time.sleep(1)
print('eating 2')
def play():
print('playing 1')
time.sleep(1)
print('playing 2')
g1 = gevent.spawn(eat) # 自动的检测阻塞事件,遇见阻塞了就会进行切换,没有阻塞就会直接结束,有些阻塞它不认识
g2 = gevent.spawn(play)
g1.join() # 阻塞直到g1结束
g2.join() # 阻塞直到g2结束
- monkey模块
- g=gevent.spawn(func,1,,2,3,x=4,y=5)创建一个协程对象g,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的
from gevent import monkey # from gevent import monkey;monkey.patch_all()
monkey.patch_all() # 识别下列阻塞,打包成一个包
import time
import gevent
def eat():
print('eating 1')
time.sleep(1)
print('eating 2')
def play():
print('playing 1')
time.sleep(1)
print('playing 2')
g1 = gevent.spawn(eat) # 自动的检测阻塞事件,遇见阻塞了就会进行切换,有些阻塞它不认识
g2 = gevent.spawn(play)
g1.join() # 阻塞直到g1结束 # gevent.joinall([g1,g2])
g2.join() # 阻塞直到g2结束
- value属性 获取返回值
spawn(函数名) 产生了一个协程任务 在遇到IO操作的时候帮助我们在多任务之间自动切换
join() 阻塞 直到某个任务被执行完毕
join_all()
from gevent import monkey;monkey.patch_all()
import time
import gevent
def eat():
print('eating 1')
time.sleep(1)
print('eating 2')
return 'eat finished'
def play():
print('playing 1')
time.sleep(1)
print('playing 2')
return 'play finished'
g1 = gevent.spawn(eat)
g2 = gevent.spawn(play)
gevent.joinall([g1,g2])
print(g1.value)
print(g2.value)
我们可以用threading.current_thread().getName()来查看每个g1和g2,查看的结果为DummyThread-n,即假线程
from gevent import monkey;monkey.patch_all()
from threading import current_thread
import threading
import gevent
import time
def eat():
print(current_thread()) # 一种方式
print('eat food 1')
time.sleep(2)
print('eat food 2')
def play():
print(threading.current_thread().getName()) # 另一种方式
print('play 1')
time.sleep(1)
print('play 2')
g1=gevent.spawn(eat)
g2=gevent.spawn(play)
gevent.joinall([g1,g2])
print('主')
爬虫例子
from gevent import monkey;monkey.patch_all()
import time
import gevent
import requests
url_lst = [
'http://www.baidu.com',
'http://www.4399.com',
'http://www.7k7k.com',
'http://www.sogou.com',
'http://www.sohu.com',
'http://www.sina.com',
'http://www.jd.com',
'https://www.luffycity.com/home',
'https://www.douban.com',
'http://www.cnblogs.com/Eva-J/articles/8324673.html',
'http://www.baidu.com',
]
def get_url(url):
response = requests.get(url)
if response.status_code == 200: # response.status_code 状态码
print(url,len(response.text)) # response.text 网页内容
# 普通方式
start = time.time()
for url in url_lst:
get_url(url)
print(time.time()-start)
# 协程方式
start = time.time()
g_lst = []
for url in url_lst:
g = gevent.spawn(get_url,url)
g_lst.append(g)
gevent.joinall(g_lst)
print(time.time()-start)
同步和异步 ***
from gevent import spawn,joinall,monkey;monkey.patch_all()
import time
def task(pid):
"""
Some non-deterministic task
"""
time.sleep(0.5)
print('Task %s done' % pid)
def synchronous():
for i in range(10):
task(i)
def asynchronous():
g_l=[spawn(task,i) for i in range(10)] # ***
joinall(g_l)
if __name__ == '__main__':
print('Synchronous:')
synchronous()
print('Asynchronous:')
asynchronous()
- 实现协程并发聊天并发
# 服务器端
from gevent import monkey;monkey.patch_all()
import socket
import gevent
from threading import current_thread
def talk(conn):
print('-->',current_thread())
while True:
conn.send(b'hello')
conn.recv(1024)
sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.listen()
while True:
conn,addr = sk.accept()
gevent.spawn(talk,conn)
# 客户端
import socket
from threading import Thread
def client():
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
while True:
print(sk.recv(1024))
sk.send(b'byebye')
for i in range(5):
Thread(target=client).start()