线程与协程

线程
进程是
    计算机中最小的资源分配单位
    进程对于操作系统来说还是有一定负担
    创建一个进程 操作系统要分配的资源大致有 :
        代码
        数据
        文件

为什么要有线程
    轻量级的概念
    他没有属于自己的进程资源:
        一条线程只负责执行代码,没有自己独立的
        代码、变量、文件资源
什么是线程
    线程是计算机中被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阻塞了就切换另外一个任务去执行,以此来提升效率。

  1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
  2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)
  3. 协程在程序之间的切换操作系统感知不到,无论开启多少个协程对操作系统来说总是一个线程
  4. 协程的本质就是一条线程,所以完全不会产生数据安全的问题(python代码作切换,而非cpu指令)
  5. (*****) 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()
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容