Python学习29——线程

Python学习29——线程

线程理论

什么是线程

进程:资源分配的最小单位
线程:CPU调度的最小单位
每一个进程中至少有一个线程

将操作系统比喻成一个大的工厂
那么进程就相当于工厂里面的车间
而线程就是车间里面的流水线

再次总结:
   进程:资源单位(起一个进程仅仅只是在内存空间中开辟一块独立的空间)
   线程:执行单位(真正被cpu执行的其实是进程里面的线程,线程指的就是代码的执行过程,执行代码中所需要使用到的资源都找所在的进程索要)
   
进程和线程都是虚拟单位,只是为了我们更加方便的描述问题

线程开销更小,更轻量级

为什么要有线程

开设进程
   1.申请内存空间   耗资源
   2.“拷贝代码”   耗资源
开线程
   一个进程内可以开设多个线程,在用一个进程内开设多个线程无需再次申请内存空间操作

总结:
   开设线程的开销要远远的小于进程的开销
   同一个进程下的多个线程数据是共享的!!!

开启线程的两种方式

第一种
from threading import Thread
import time

def task():
    print('开始')
    time.sleep(1)
    print('结束')


if __name__ == '__main__':
    t=Thread(target=task,) # 实例化得到一个对象
    t.start() # 对象.start()启动线程
    print('主')

第二种,通过类继承的方式
from threading import Thread
import time

class MyThread(Thread):
    def run(self):
        print('开始')
        time.sleep(1)
        print('结束')

if __name__ == '__main__':
    t=MyThread()
    t.start()
    print('主')

实现TCP服务端并发的效果

join的使用



from threading import Thread

import time
def task(n):
    print('开始')
    time.sleep(n)
    print('结束')


if __name__ == '__main__':
    t=Thread(target=task,args=(2,))
    t.start()


    t1=Thread(target=task,args=(3,))
    t1.start()


    t.join()  # 等待子进程执行结束
    t1.join()
    print('主')

同一个进程下的多个线程数据共享

from threading import Thread

import time

money = 99


def task(n):
    global money
    money=n
    print('开始')
    # time.sleep(n)
    print('结束')


if __name__ == '__main__':
    t = Thread(target=task, args=(2,))
    t.start()

    t1 = Thread(target=task, args=(66,))
    t1.start()


    t.join()
    t1.join()
    print(money)
    print('主')

线程对象属性及其方法

os.getpid()

查看当前进程的ID

from threading import Thread, active_count, current_thread
import os
import time


def task():
    print('hello world', os.getpid())


if __name__ == '__main__':
    t = Thread(target=task)
    t.start()
    print('主', os.getpid())     # 查看当前进程的ID,如果一样,就说明在同一个进程下

# hello world 3604
# 主 3604

current_thread().name

查看当前线程名称

from threading import Thread, active_count, current_thread
import os
import time


def task():
    print('hello world', current_thread().name)


if __name__ == '__main__':
    t = Thread(target=task)
    t.start()
    print('主', current_thread().name)     # 查看当前线程名称


# hello world主 MainThread
#  Thread-1

查看当前线程名称 - 多个线程

from threading import Thread, active_count, current_thread
import os
import time


def task():
    print('hello world', current_thread().name)


if __name__ == '__main__':
    t = Thread(target=task)
    t1 = Thread(target=task)
    t2 = Thread(target=task)
    t.start()
    t1.start()
    t2.start()
    print('主', current_thread().name)     # 查看当前线程名称


# hello world Thread-1
# hello world Thread-2
# hello world Thread-3
# 主 MainThread

active_count()
当前进程下有几个线程存活

from threading import Thread, active_count, current_thread
import os
import time


def task(n):
    print('hello world', current_thread().name)
    time.sleep(n)


if __name__ == '__main__':
    t = Thread(target=task, args=(1,))
    t1 = Thread(target=task, args=(2,))
    t.start()
    t1.start()
    t1.join()
    print('主', active_count())     # 统计当前正在活跃的线程数


# hello world Thread-1
# hello world Thread-2
# 主 1

# 说明:t1睡2S,但是t睡了1S后,运行结束,此时只剩下了t1,所以此时活跃的线程只有1个

总结

from threading import Thread, current_thread,active_count
import time
import os


def task(n):
    print('开始')
    print(current_thread().name)   # 线程名字
    # 如果打印进程id号,会是什么
    print(os.getpid())
    time.sleep(n)
    print('结束')



if __name__ == '__main__':
    t1 = Thread(target=task,name='egon',args=(2,))
    t2 = Thread(target=task,args=(8,))
    t1.start()
    t2.start()
    t1.join()
    print('---------',t1.is_alive())
    print('---------',t2.is_alive())

    # 当作线程id号
    print('*********',t1.ident)
    print('*********',t2.ident)

    print(os.getpid())
    print(active_count())  # 打印出3 ,开了两个线程,还有一个主线程


	必须知道的
	1 线程t.name  t.getName()
	2 当前进程下有几个线程存活active_count
	3 t1.is_alive() 当前线程是否存活
	4 t1.ident  当作是线程id

守护线程

from threading import Thread, current_thread,active_count
import time
import os


def task(n):
    print('开始')
    time.sleep(n)
    # print('-----',active_count())
    print('结束')



if __name__ == '__main__':
    t1 = Thread(target=task,name='egon',args=(10,))
    # t1.daemon = True
    t1.setDaemon(True)
    t1.start()

    t2 = Thread(target=task,name='egon',args=(4,))
    t2.start()

    print('主')

线程互斥锁

from threading import Thread,Lock
import time
import random

money = 99


def task(n,mutex):
    global money
    # 在修改数据的时候,枷锁
    mutex.acquire()
    temp = money
    time.sleep(0.1)

    money = temp - 1
    # 修改完以后,释放锁,其它线程就能再次抢到锁
    mutex.release()


if __name__ == '__main__':
    ll=[]
    mutex=Lock()
    for i in range(10):
        t = Thread(target=task, args=(i,mutex))
        t.start()
        ll.append(t)

    for i in ll:
        i.join()

    print(money)

GIL全局解释器锁理论

验证GIL锁的存在方式

from threading import Thread
from multiprocessing import Process

def task():
    while True:
        pass

if __name__ == '__main__':
    for i in range(6):
        # t=Thread(target=task)  # 因为有GIL锁,同一时刻,只有一条线程执行,所以cpu不会满
        t=Process(target=task)   # 由于是多进程,进程中的线程会被cpu调度执行,6个cpu全在工作,就会跑满
        t.start()

GIL与普通互斥锁的区别

GIL锁是不能保证数据的安全,普通互斥锁来保证数据安全
from threading import Thread, Lock
import time

mutex = Lock()
money = 100


def task():
    global money
    mutex.acquire()
    temp = money
    time.sleep(1)
    money = temp - 1
    mutex.release()


if __name__ == '__main__':
    ll=[]
    for i in range(10):
        t = Thread(target=task)
        t.start()
        # t.join()  # 会怎么样?变成了串行,不能这么做
        ll.append(t)
    for t in ll:
        t.join()
    print(money)

io密集型和计算密集型

-----以下只针对于cpython解释器
-在单核情况下:
-开多线程还是开多进程?不管干什么都是开线程
-在多核情况下:
-如果是计算密集型,需要开进程,能被多个cpu调度执行
-如果是io密集型,需要开线程,cpu遇到io会切换到其他线程执行

计算密集型

from threading import Thread
from multiprocessing import Process
import time

def task():
    count = 0
    for i in range(100000000):
        count += i


if __name__ == '__main__':
    ctime = time.time()
    ll = []
    for i in range(10):
        t = Thread(target=task)  # 开线程:42.68658709526062
        # t = Process(target=task)   # 开进程:9.04949426651001
        t.start()
        ll.append(t)

    for t in ll:
        t.join()
    print(time.time()-ctime)

io密集型

from threading import Thread
from multiprocessing import Process
import time


def task():
    time.sleep(2)


if __name__ == '__main__':
    ctime = time.time()
    ll = []
    for i in range(400):
        t = Thread(target=task)  # 开线程:2.0559656620025635
        # t = Process(target=task)   # 开进程:9.506720781326294
        t.start()
        ll.append(t)

    for t in ll:
        t.join()
    print(time.time()-ctime)

死锁现象(哲学家就餐问题)

是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁

更多单例模式:https://www.cnblogs.com/liuqingzheng/p/10038958.html
# 死锁现象,张三拿到了A锁,等B锁,李四拿到了B锁,等A锁
from threading import Thread, Lock
import time

mutexA = Lock()
mutexB = Lock()


def eat_apple(name):
    mutexA.acquire()
    print('%s 获取到了a锁' % name)
    mutexB.acquire()
    print('%s 获取到了b锁' % name)
    print('开始吃苹果,并且吃完了')
    mutexB.release()
    print('%s 释放了b锁' % name)
    mutexA.release()
    print('%s 释放了a锁' % name)


def eat_egg(name):
    mutexB.acquire()
    print('%s 获取到了b锁' % name)
    time.sleep(2)
    mutexA.acquire()
    print('%s 获取到了a锁' % name)
    print('开始吃鸡蛋,并且吃完了')
    mutexA.release()
    print('%s 释放了a锁' % name)
    mutexB.release()
    print('%s 释放了b锁' % name)


if __name__ == '__main__':
    ll = ['egon', 'alex', '铁蛋']
    for name in ll:
        t1 = Thread(target=eat_apple, args=(name,))
        t2 = Thread(target=eat_egg, args=(name,))
        t1.start()
        t2.start()

递归锁

递归锁(可重入),同一个人可以多次acquire,每acquire一次,内部计数器加1,每relaese一次,内部计数器减一
只有计数器不为0,其他人都不获得这把锁

from threading import Thread, Lock,RLock
import time

# 同一把锁
# mutexA = Lock()
# mutexB = mutexA

# 使用可重入锁解决(同一把锁)
# mutexA = RLock()
# mutexB = mutexA
mutexA = mutexB =RLock()

def eat_apple(name):
    mutexA.acquire()
    print('%s 获取到了a锁' % name)
    mutexB.acquire()
    print('%s 获取到了b锁' % name)
    print('开始吃苹果,并且吃完了')
    mutexB.release()
    print('%s 释放了b锁' % name)
    mutexA.release()
    print('%s 释放了a锁' % name)


def eat_egg(name):
    mutexB.acquire()
    print('%s 获取到了b锁' % name)
    time.sleep(2)
    mutexA.acquire()
    print('%s 获取到了a锁' % name)
    print('开始吃鸡蛋,并且吃完了')
    mutexA.release()
    print('%s 释放了a锁' % name)
    mutexB.release()
    print('%s 释放了b锁' % name)


if __name__ == '__main__':
    ll = ['egon', 'alex', '铁蛋']
    for name in ll:
        t1 = Thread(target=eat_apple, args=(name,))
        t2 = Thread(target=eat_egg, args=(name,))
        t1.start()
        t2.start()

Semaphore信号量

Semaphore:信号量可以理解为多把锁,同时允许多个线程来更改数据
from  threading import Thread,Semaphore
import time
import random
sm=Semaphore(3) # 数字表示可以同时有多少个线程操作

def task(name):
    sm.acquire()
    print('%s 正在上大号'%name)
    time.sleep(random.randint(1,5))
    sm.release()

Event事件

一些线程需要等到其他线程执行完成之后才能执行,类似于发射信号
比如一个线程等待另一个线程执行结束再继续执行
一些线程需要等到其他线程执行完成之后才能执行,类似于发射信号
比如一个线程等待另一个线程执行结束再继续执行

from threading import Thread, Event
import time

event = Event()


def girl(name):
    print('%s 现在不单身,正在谈恋爱'%name)
    time.sleep(10)
    print('%s 分手了,给男发了信号'%name)
    event.set()


def boy(name):
    print('%s 在等着小姐姐分手'%name)
    event.wait()  # 只要没来信号,就卡在者
    print('小姐姐分手了,机会来了,冲啊')


if __name__ == '__main__':
    lyf = Thread(target=girl, args=('小姐姐',))
    lyf.start()

    for i in range(10):
        b = Thread(target=boy, args=('男%s号' % i,))
        b.start()

起两个线程,第一个线程读文件的前半部分,读完发一个信号,另一个进程读后半部分,并打印

from threading import Thread, Event
import time
import os

event = Event()
# 获取文件总大小
size = os.path.getsize('a.txt')


def read_first():
    with open('a.txt', 'r', encoding='utf-8') as f:
        n = size // 2  # 取文件一半,整除
        data = f.read(n)
        print(data)
        print('我一半读完了,发了个信号')
        event.set()


def read_last():
    event.wait()  # 等着发信号
    with open('a.txt', 'r', encoding='utf-8') as f:
        n = size // 2  # 取文件一半,整除
        # 光标从文件开头开始,移动了n个字节,移动到文件一半
        f.seek(n, 0)
        data = f.read()
        print(data)


if __name__ == '__main__':
    t1=Thread(target=read_first)
    t1.start()
    t2=Thread(target=read_last)
    t2.start()

线程queue

进程queue和线程不是一个

from multiprocessing import Queue

线程queue

from queue import Queue,LifoQueue,PriorityQueue

线程间通信,因为共享变量会出现数据不安全问题,用线程queue通信,不需要加锁,内部自带
queue是线程安全的

三种线程Queue
    -Queue:队列,先进先出
    -PriorityQueue:优先级队列,谁小谁先出
    -LifoQueue:栈,后进先出

如何使用

q=Queue(5)
q.put("lqz")
q.put("egon")
q.put("铁蛋")
q.put("钢弹")
q.put("金蛋")


# q.put("银蛋")
# q.put_nowait("银蛋")
# 取值
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
# 卡住
# print(q.get())
# q.get_nowait()
# 是否满,是否空
print(q.full())
print(q.empty())

LifoQueue

q=LifoQueue(5)
q.put("lqz")
q.put("egon")
q.put("铁蛋")
q.put("钢弹")
q.put("金蛋")
#
# q.put("ddd蛋")
print(q.get())


PriorityQueue:数字越小,级别越高

q=PriorityQueue(3)
q.put((-10,'金蛋'))
q.put((100,'银蛋'))
q.put((101,'铁蛋'))
# q.put((1010,'铁dd蛋'))  # 不能再放了

print(q.get())
print(q.get())
print(q.get())

线程池进程池

为什么会出现池?
不管是开进程还是开线程,不能无限制开,通过池,假设池子里就有10个,不管再怎么开,永远是这10个

如何使用

from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
from threading import Thread
import time
import random

pool = ThreadPoolExecutor(5)  # 数字是池的大小
# pool = ProcessPoolExecutor(5)  # 数字是池的大小


def task(name):
    print('%s任务开始' % name)

    time.sleep(random.randint(1, 4))
    print('任务结束')
    return '%s 返回了'%name



def call_back(f):
    # print(type(f))
    print(f.result())
if __name__ == '__main__':

    # ll=[]
    # for i in range(10):  # 起了100个线程
    #     # t=Thread(target=task)
    #     # t.start()
    #     res = pool.submit(task, '男%s号' % i)  # 不需要再写在args中了
    #     # res是Future对象
    #     # from  concurrent.futures._base import Future
    #     # print(type(res))
    #     # print(res.result())  # 像join,只要执行result,就会等着结果回来,就变成串行了
    #     ll.append(res)
    #
    # for res in ll:
    #     print(res.result())

    # 终极使用
    for i in range(10):  # 起了100个线程
        # 向线程池中提交一个任务,等任务执行完成,自动回到到call_back函数执行
        pool.submit(task,'男%s号' % i).add_done_callback(call_back)

进程池如何使用

from concurrent.futures import ProcessPoolExecutor
pool = ProcessPoolExecutor(2)
pool.submit(get_pages, url).add_done_callback(call_back)

线程池小案例

from concurrent.futures import ThreadPoolExecutor

import requests  # 爬虫会学到的模块

pool = ThreadPoolExecutor(2)


def get_pages(url):
    # https://www.baidu.com
    res = requests.get(url)  # 向这个地址发送请求

    name = url.rsplit('/')[-1] + '.html'
    print(name)  # www.baidu.com.html
    # res.content拿到页面的二进制
    return {'name': name, 'text': res.content}


def call_back(f):
    dic = f.result()
    with open(dic['name'], 'wb') as f:
        f.write(dic['text'])


if __name__ == '__main__':
    ll = ['https://www.baidu.com', 'https://www.cnblogs.com']
    for url in ll:
        pool.submit(get_pages, url).add_done_callback(call_back)

线程池shutdown

from concurrent.futures import ThreadPoolExecutor
import time

pool = ThreadPoolExecutor(3)


def task(name):
    print('%s 开始'%name)
    time.sleep(1)
    print('%s 结束'%name)


if __name__ == '__main__':
    for i in range(20):
        pool.submit(task, '男%s' % i)

    # 放到for外面,等待所有任务执行完成,主线程再继续走
    pool.shutdown(wait=True)  # 等待所有任务完成,并且把池关闭
    # # 问题,关了还能提交任务吗?不能再提交了
    # pool.submit(task,'sdddd')
    print('主') # 立马执行,20个线程都执行完了,再执行

定时器

# 多长时间之后执行一个任务
from threading import Timer


def task(name):
    print('我是大帅比--%s'%name)


if __name__ == '__main__':
    # t = Timer(2, task,args=('lqz',))  # 本质是开两个线程,延迟一秒执行
    t = Timer(2, task,kwargs={'name':'lqz'})  # 本质是开两个线程,延迟一秒执行
    t.start()

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值