阅读目录

  • 操作系统线程理论
  • 线程概念的引入背景
  • 进程和线程的关系
  • 线程的特点(了解)
  • Python中的线程
  • 理论知识
  • GIL全局解释器锁
  • python线程模块的选择
  • threading模块
  • 线程的创建Threading.Thread类
  • 线程的创建
  • 多线程与多进程
  • 多线程的应用场景
  • Thread类的其他方法
  • 方法概述
  • threading.local()
  • join()
  • 守护线程
  • setDaemon
  • daemon
  • 锁 机制
  • 初始线程中的锁(非全局解释器锁)
  • 同步锁
  • 死锁与递归锁
  • 死锁
  • 递归锁
  • BoundedSemaphore 信号量
  • Condition
  • Timer和Event
  • 线程队列
  • Python标准模块:concurrent.futures
  • ThreadPoolExecutor 线程池
  • 线程池用法--爬虫实例
  • 安装模块
  • 爬虫示例
  • map的用法
  • 回调函数
  • 练习题


操作系统线程理论

线程概念的引入背景

python 创建线程池 销毁线程池 python 线程结束_python 创建线程池 销毁线程池

操作系统中的多线程

python 创建线程池 销毁线程池 python 线程结束_多线程_02

进程和线程的关系

python 创建线程池 销毁线程池 python 线程结束_python 创建线程池 销毁线程池_03


python 创建线程池 销毁线程池 python 线程结束_Python线程_04

线程的特点(了解)

python 创建线程池 销毁线程池 python 线程结束_Python多线程实现_05


python 创建线程池 销毁线程池 python 线程结束_多线程_06

用户级与内核级线程的对比

# 用户级线程和内核级线程的区别
'''
内核支持线程是OS内核可感知的,而用户级线程是OS内核不可感知的。
用户级线程的创建、撤消和调度不需要OS内核的支持,是在语言(如Java)这一级处理的;
而内核支持线程的创建、撤消和调度都需OS内核提供支持,而且与进程的创建、撤消和调度大体是相同的。
用户级线程执行系统调用指令时将导致其所属进程被中断,
	而内核支持线程执行系统调用指令时,只导致该线程被中断。
在只有用户级线程的系统内,CPU调度还是以进程为单位,处于运行状态的进程中的多个线程,
由用户程序控制线程的轮换运行;在有内核支持线程的系统内,CPU调度则以线程为单位,
由OS的线程调度程序负责线程的调度。
用户级线程的程序实体是运行在用户态下的程序,
	而内核支持线程的程序实体则是可以运行在任何状态下的程序。
'''

# 内核线程的优缺点
'''
优点:当有多个处理机时,一个进程的多个线程可以同时执行。
缺点:由内核进行调度。
'''
# 用户级线程的优缺点
'''
优点:
线程的调度不需要内核直接参与,控制简单。
可以在不支持线程的操作系统中实现。
创建和销毁线程、线程切换代价等线程管理的代价比内核线程少得多。
允许每个进程定制自己的调度算法,线程管理比较灵活。
线程能够利用的表空间和堆栈空间比内核级线程多。
同一进程中只能同时有一个线程在运行,如果有一个线程使用了系统调用而阻塞,
那么整个进程都会被挂起。另外,页面失效也会产生同样的问题。

缺点:
资源调度按照进程进行,多个处理机下,同一个进程中的线程只能在同一个处理机下分时复用
'''

Python中的线程

理论知识

GIL全局解释器锁

python 创建线程池 销毁线程池 python 线程结束_多线程相关_07


python 创建线程池 销毁线程池 python 线程结束_Python多线程实现_08


python 创建线程池 销毁线程池 python 线程结束_多线程_09

'''
GIL锁,全局解释器锁。用于限制一个进程中同一时刻只有一个线程被cpu调度。

扩展:默认GIL锁在执行100个cpu指令(过期时间)。
     import sys
     v1 = sys.getcheckinterval()
     print(v1) # 100
'''
python线程模块的选择

python 创建线程池 销毁线程池 python 线程结束_多线程相关_10

threading模块

multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性

线程的创建Threading.Thread类
线程的创建
# 创建线程的方式1

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=('jaychou',))
    t.start()
    print('主线程') # 主线程默认等子线程执行完毕
# 创建线程的方式2--面向对象的方式

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('jaychou')
    t.start()
    print('主线程') # 主线程默认等子线程执行完毕

线程的本质

import threading


# 先打印:11?123?
def func(arg):
    print(arg)


t1 = threading.Thread(target=func, args=(11,))
t1.start()
# start 是开始运行线程吗?不是
# start 告诉cpu,我已经准备就绪,你可以调度我了。
print(123)

不使用多线程和使用多线程对比

# 不使用

import requests
import uuid

url_list = [
    'https://www3.autoimg.cn/newsdfs/g28/M05/F9/98/120x90_0_autohomecar__ChsEnluQmUmARAhAAAFES6mpmTM281.jpg',
    'https://www2.autoimg.cn/newsdfs/g28/M09/FC/06/120x90_0_autohomecar__ChcCR1uQlD6AT4P3AAGRMJX7834274.jpg',
    'https://www2.autoimg.cn/newsdfs/g3/M00/C6/A9/120x90_0_autohomecar__ChsEkVuPsdqAQz3zAAEYvWuAspI061.jpg',
]


def task(url):
    ret = requests.get(url)
    file_name = str(uuid.uuid4()) + '.jpg'
    with open(file_name, mode='wb') as f:
        f.write(ret.content)


for url in url_list:
    task(url)

"""
- 你写好代码
- 交给解释器运行: python s1.py
- 解释器读取代码,再交给操作系统去执行,
	根据你的代码去选择创建多少个线程/进程去执行(单进程/单线程)。
- 操作系统调用硬件:硬盘、cpu、网卡....
"""
# 使用
import threading
import requests
import uuid

url_list = [
    'https://www3.autoimg.cn/newsdfs/g28/M05/F9/98/120x90_0_autohomecar__ChsEnluQmUmARAhAAAFES6mpmTM281.jpg',
    'https://www2.autoimg.cn/newsdfs/g28/M09/FC/06/120x90_0_autohomecar__ChcCR1uQlD6AT4P3AAGRMJX7834274.jpg',
    'https://www2.autoimg.cn/newsdfs/g3/M00/C6/A9/120x90_0_autohomecar__ChsEkVuPsdqAQz3zAAEYvWuAspI061.jpg',
]


def task(url):
    ret = requests.get(url)
    file_name = str(uuid.uuid4()) + '.jpg'
    with open(file_name, mode='wb') as f:
        f.write(ret.content)


for url in url_list:
    t = threading.Thread(target=task, args=(url,))
    t.start()
多线程与多进程
# pid的比较

from threading import Thread
from multiprocessing import Process
import os


def work():
    print('hello', os.getpid())


if __name__ == '__main__':
    # part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样
    t1 = Thread(target=work)
    t2 = Thread(target=work)
    t1.start()
    t2.start()
    print('主线程/主进程pid', os.getpid())

    # part2:开多个进程,每个进程都有不同的pid
    p1 = Process(target=work)
    p2 = Process(target=work)
    p1.start()
    p2.start()
    print('主线程/主进程pid', os.getpid())
    
'''
hello 7860
hello 7860
主线程/主进程pid 7860
主线程/主进程pid 7860
hello 10036
hello 13944
'''
# 开启效率的较量

from threading import Thread
from multiprocessing import Process
import os


def work():
    print('hello')


if __name__ == '__main__':
    # 在主进程下开启线程
    t = Thread(target=work)
    t.start()
    print('主线程/主进程')
    '''
    打印结果:
    hello
    主线程/主进程
    '''

    # 在主进程下开启子进程
    t = Process(target=work)
    t.start()
    print('主线程/主进程')
    '''
    打印结果:
    主线程/主进程
    hello
    '''
# 内存数据的共享问题

from threading import Thread
from multiprocessing import Process
import os


def work():
    global n
    n = 0


if __name__ == '__main__':
    # n=100
    # p=Process(target=work)
    # p.start()
    # p.join()
    # print('主',n) 毫无疑问子进程p已经将自己的全局的n改成了0,
    # --但改的仅仅是它自己的,查看父进程的n仍然为100

    n = 1
    t = Thread(target=work)
    t.start()
    t.join()
    print('主', n)  # 查看结果为0,因为同一进程内的线程之间共享进程内的数据
# 同一进程内的线程共享该进程的数据?
'''
Python多线程情况下:
- 计算密集型操作:效率低。(GIL锁限制的,一个进程中只要一个线程被调用)
- IO操作: 效率高

Python多进程的情况下:
- 计算密集型操作:效率高(浪费资源)。 不得已而为之。
- IO操作: 效率高 (浪费资源)。

以后写Python时:(c python)
     IO密集型用多线程: 文件/输入输出/socket网络通信
     计算密集型用多进程。
	(pypy解决了这个问题)

扩展:
   Java多线程情况下:
	   - 计算密集型操作:效率高。
	   - IO操作: 效率高
   java多进程的情况下:(很少写)
	   - 计算密集型操作:效率高(浪费资源)。
	   - IO操作: 效率高 浪费资源)。
'''

多线程实现socket

# server端

# _*_coding:utf-8_*_
# !/usr/bin/env python
import multiprocessing
import threading

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 8080))
s.listen(5)


def action(conn):
    while True:
        data = conn.recv(1024)
        print(data)
        conn.send(data.upper())


if __name__ == '__main__':

    while True:
        conn, addr = s.accept()

        p = threading.Thread(target=action, args=(conn,))
        p.start()
# client

# _*_coding:utf-8_*_
# !/usr/bin/env python


import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 8080))

while True:
    msg = input('>>: ').strip()
    if not msg: continue

    s.send(msg.encode('utf-8'))
    data = s.recv(1024)
    print(data)
多线程的应用场景

计算密集型多线程–多线程无用

import threading

v1 = [11, 22, 33]  # +1
v2 = [44, 55, 66]  # +100


def func(data, plus):
    for i in range(len(data)):
        data[i] = data[i] + plus


t1 = threading.Thread(target=func, args=(v1, 1))
t1.start()

t2 = threading.Thread(target=func, args=(v2, 100))
t2.start()

IO操作–多线程有用
例子:如上面的 爬虫示例

Thread类的其他方法
方法概述
'''
Thread实例对象的方法
  # isAlive(): 返回线程是否活动的。
  # getName(): 返回线程名。
  # setName(): 设置线程名。

threading模块提供的一些方法:
  # threading.currentThread(): 返回当前的线程变量。
  # threading.enumerate(): 返回一个包含正在运行的线程的list。
  # --正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  # threading.activeCount(): 返回正在运行的线程数量,
  # --与len(threading.enumerate())有相同的结果。
'''
# 代码示例

from threading import Thread
import threading
from multiprocessing import Process
import os


def work():
    import time
    time.sleep(3)
    print(threading.current_thread().getName()) # 获取名字


if __name__ == '__main__':
    # 在主进程下开启线程
    t = Thread(target=work)
    t.start()

    print(threading.current_thread().getName())
    print(threading.current_thread())  # 主线程
    print(threading.enumerate())  # 连同主线程在内有两个运行的线程
    print(threading.active_count())
    print('主线程/主进程')

    '''
    打印结果:
    MainThread
    <_MainThread(MainThread, started 140735268892672)>
    [<_MainThread(MainThread, started 140735268892672)>, 
    <Thread(Thread-1, started 123145307557888)>]
    主线程/主进程
    Thread-1
    '''
threading.local()

引入前先了解一下局部变量和全局变量

# 局部变量 

import threading
import time
 
def worker():
    x = 0
    for i in range(100):
        time.sleep(0.0001)
        x += 1
    print(threading.current_thread(),x)
 
for i in range(10):
    threading.Thread(target=worker).start()
 
运行结果:
<Thread(Thread-2, started 123145372971008)> 100
<Thread(Thread-6, started 123145393991680)> 100
<Thread(Thread-1, started 123145367715840)> 100
<Thread(Thread-3, started 123145378226176)> 100
<Thread(Thread-5, started 123145388736512)> 100
<Thread(Thread-7, started 123145399246848)> 100
<Thread(Thread-4, started 123145383481344)> 100
<Thread(Thread-10, started 123145415012352)> 100
<Thread(Thread-8, started 123145404502016)> 100
<Thread(Thread-9, started 123145409757184)> 100

'''
上面例子使用多线程,每个子线程完成不同的计算任务,x是局部变量。

每个子线程都要压栈,每个栈是独立的空间。每次压栈,局部变量x的作用域地址是不同的
(线程独享),计算结果互不干扰
'''
# 全局变量

import threading
import time
 
x = 0
def worker():
    global x
    x = 0
    for i in range(100):
        time.sleep(0.0001)
        x += 1
    print(threading.current_thread(),x)
 
for i in range(10):
    threading.Thread(target=worker).start()
 
运行结果:
<Thread(Thread-2, started 123145483571200)> 888
<Thread(Thread-5, started 123145499336704)> 908
<Thread(Thread-3, started 123145488826368)> 930
<Thread(Thread-4, started 123145494081536)> 937
<Thread(Thread-1, started 123145478316032)> 941
<Thread(Thread-6, started 123145504591872)> 947
<Thread(Thread-7, started 123145509847040)> 949
<Thread(Thread-8, started 123145515102208)> 955
<Thread(Thread-9, started 123145520357376)> 962
<Thread(Thread-10, started 123145525612544)> 964

'''
上面例子中当主线程中x是全局变量时,就变成了公共资源(也就是同一个对象),
每个子线程互相干扰,最终导致错误的计算结果
'''

Python提供了 threading.local 类,将这个类实例化得到一个全局对象,
但是不同的线程使用这个对象存储的数据其它线程不可见
(本质上就是不同的线程使用这个对象时为其创建一个独立的字典)

# 使用threading.local() 
import time
import threading

v = threading.local()


def func(arg):
    # 内部会为当前线程创建一个空间用于存储:phone=自己的值
    v.phone = arg
    time.sleep(2)
    print(v.phone, arg)  # 去当前线程自己空间取值


for i in range(10):
    t = threading.Thread(target=func, args=(i,))
    t.start()
# 使用threading.local() 
import threading
import time
 
# class A:
#     def __init__(self,x):
#         self.x = x
# a = A(0)
 
a = threading.local() # 全局对象
 
def worker():
    a.x = 0
    for i in range(100):
        time.sleep(0.0001)
        a.x += 1
    print(threading.current_thread(),a.x)
 
for i in range(10):
    threading.Thread(target=worker).start()
 
# 运行结果:
<Thread(Thread-4, started 123145570172928)> 100
<Thread(Thread-6, started 123145580683264)> 100
<Thread(Thread-1, started 123145554407424)> 100
<Thread(Thread-2, started 123145559662592)> 100
<Thread(Thread-8, started 123145591193600)> 100
<Thread(Thread-5, started 123145575428096)> 100
<Thread(Thread-3, started 123145564917760)> 100
<Thread(Thread-7, started 123145585938432)> 100
<Thread(Thread-10, started 123145601703936)> 100
<Thread(Thread-9, started 123145596448768)> 100

# 每个子线程使用全局对象a,但每个线程定义的属性a.x是该线程独有的

threading.local原理解析

import time
import threading

DATA_DICT = {}  # 内部就是通过一个字典来实现


def func(arg):
    c = threading.current_thread()
    print(c, arg)
    ident = threading.get_ident()  # 直接获取到线程ID
    DATA_DICT[ident] = arg
    time.sleep(1)  # 上面线程已经全部执行完毕
    print(DATA_DICT[ident], arg)  # 再通过字典来取值


for i in range(10):
    t = threading.Thread(target=func, args=(i,))
    t.start()
'''
<Thread(Thread-1, started 11536)> 0
<Thread(Thread-2, started 4332)> 1
<Thread(Thread-3, started 8588)> 2
<Thread(Thread-4, started 2980)> 3
<Thread(Thread-5, started 9588)> 4
<Thread(Thread-6, started 8092)> 5
<Thread(Thread-7, started 11124)> 6
<Thread(Thread-8, started 13152)> 7
<Thread(Thread-9, started 11904)> 8
<Thread(Thread-10, started 7920)> 9
2 2
3 3
1 1
0 0
7 7
6 6
5 5
4 4
9 9
8 8
'''
import time
import threading

INFO = {}


class Local(object):

    def __getattr__(self, item):
        ident = threading.get_ident()
        return INFO[ident][item]

    def __setattr__(self, key, value):  # key是phone,valueshi arg
        ident = threading.get_ident()
        if ident in INFO:
            INFO[ident][key] = value
        else:
            INFO[ident] = {key: value}  # 字典嵌套字典,一个ID对应一个字典


obj = Local()


def func(arg):
    obj.phone = arg  # 调用对象的 __setattr__方法(“phone”,1);给对象的属性赋值时调用
    time.sleep(2)
    print(obj.phone, arg)


for i in range(10):
    t = threading.Thread(target=func, args=(i,))
    t.start()

一个错误的例子

import threading

X = 'abc'
ctx = threading.local()
ctx.x = 123  # 主线程中定义x本地属性
print(ctx, type(ctx), ctx.x)


def work():
    print(X)
    print(ctx)
    print(ctx.x)  # 子线程访问不到
    print('Good job')


threading.Thread(target=work).start()

python 创建线程池 销毁线程池 python 线程结束_Python多线程实现_11

ctx全局对象对主线程和子线程都是可以使用的,主线程定义了属性x,
但子线程在尝试访问属性x时,就相当于访问自己线程内的属性x,
而自己线程并没有定义,就会抛出AttributeError异常:
‘_thread._local’ object has no attribute ‘x’

join()
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=('jaychou',))
    t.start()
    t.join()
    print('主线程')
    print(t.is_alive())
    '''
    jaychou say hello
    主线程
    False
    '''

开发者可以控制主线程等待子线程(最多等待时间)

import time
import threading


def func(arg):
    time.sleep(3)
    print(arg)


if __name__ == '__main__':
    print('创建子线程t1')
    t1 = threading.Thread(target=func, args=(3,))
    t1.start()
    # 无参数,让主线程在这里等着,等到子线程t1执行完毕,才可以继续往下走。
    t1.join()

    print("主线程1执行完毕")

    print('创建子线程t2')
    t2 = threading.Thread(target=func, args=(9,))
    t2.start()
    # 有参数,让主线程在这里最多等待n秒,无论是否执行完毕,会继续往下走。
    t2.join(4)

    print("主线程2执行完毕")
    
'''
创建子线程t1
3
主线程1执行完毕
创建子线程t2
9
主线程2执行完毕
'''
守护线程

python 创建线程池 销毁线程池 python 线程结束_多线程_12

setDaemon
# 守护线程例1 setDemon
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.setDaemon(True)  # 必须在t.start()之前设置
    t.start()

    print('主线程')
    print(t.is_alive())
    '''
    主线程
    True
    '''
daemon
# 守护线程例2 daemon
from threading import Thread
import time


def foo():
    print(123)
    time.sleep(1)
    print("end123")


def bar():
    print(456)
    time.sleep(3)
    print("end456")


t1 = Thread(target=foo)
t2 = Thread(target=bar)

t1.daemon = True
t1.start()
t2.start()
print("main-------")

'''
123
456
main-------
end123
end456
'''

锁 机制

初始线程中的锁(非全局解释器锁)

多个线程抢占资源的情况

from threading import Thread
import os, time


def work():
    global n
    temp = n
    time.sleep(0.1)
    n = temp - 1


if __name__ == '__main__':
    n = 100
    l = []
    for i in range(100):
        p = Thread(target=work)
        l.append(p)
        p.start()
    for p in l:
        p.join()

    print(n)  # 结果可能为99
import threading

n = 10


def task(i):
    global n
    print('当前线程', i, '读取到的n值为:', n)
    n = i  # 本质:虽然没有加锁,由于这个赋值过于简单,cpu切换在较短时间内完成了,
    		# 如果过于复杂就会报错
    print('当前线程', i, '修改n值为:', n)


for i in range(10):
    t = threading.Thread(target=task, args=(i,))
    t.start()
    
'''
当前线程 0 读取到的n值为: 10
当前线程 0 修改n值为: 0
当前线程 1 读取到的n值为: 0
当前线程 1 修改n值为: 1
当前线程 2 读取到的n值为: 1
当前线程 2 修改n值为: 2
当前线程 3 读取到的n值为: 2
当前线程 3 修改n值为: 3
当前线程 4 读取到的n值为: 3
当前线程 4 修改n值为: 4
当前线程 5 读取到的n值为: 4
当前线程 5 修改n值为: 5
当前线程 6 读取到的n值为: 5
当前线程 6 修改n值为: 6
当前线程 7 读取到的n值为: 6
当前线程 7 修改n值为: 7
当前线程 8 读取到的n值为: 7
当前线程 8 修改n值为: 8
当前线程 9 读取到的n值为: 8
当前线程 9 修改n值为: 9
'''

深入理解线程中的锁

'''
线程安全:多线程操作时,内部会让所有线程排队处理。如:list/dict/queue
线程不安全 + 人 => 排队处理。也可以做到 线程安全

需求:
    a. 创建100个线程,在列表中追加8(append 安全的)
    b. 创建100个线程 
    v = []
    锁 
    - 把自己的添加到列表中。(这时候也用append,从尾部添加)
    - 在读取列表的最后一个。# 放的时候是安全的,为保证放入后也取到是自己的,
    # --所以需要加锁,自己放完,然后取回自己的
    解锁
'''
import threading
import time

n = 10


def task(i):
    global n
    print('当前线程', i, '读取到的n值为:', n)  
    # 全部停在这里了,10个线程马上就进来了,全部运行完毕
    n = i  # 最后一次变成了 9
    time.sleep(1)  
    # 加个停顿模拟复杂运算,则结果就会发生变化,cup切换导致出错
    print('当前线程', i, '修改n值为:', n)


for i in range(10):
    t = threading.Thread(target=task, args=(i,))
    t.start()

'''
当前线程 0 读取到的n值为: 10
当前线程 1 读取到的n值为: 0
当前线程 2 读取到的n值为: 1
当前线程 3 读取到的n值为: 2
当前线程 4 读取到的n值为: 3
当前线程 5 读取到的n值为: 4
当前线程 6 读取到的n值为: 5
当前线程 7 读取到的n值为: 6
当前线程 8 读取到的n值为: 7
当前线程 9 读取到的n值为: 8
当前线程 0 修改n值为: 9  # 过完1秒才打印,最后一次修改的值
当前线程 1 修改n值为: 9
当前线程 2 修改n值为: 9
当前线程 4 修改n值为: 9
当前线程 3 修改n值为: 9
当前线程 7 修改n值为: 9
当前线程 8 修改n值为: 9
当前线程 5 修改n值为: 9
当前线程 6 修改n值为: 9
当前线程 9 修改n值为: 9
'''
同步锁

一次放一个
Lock 是锁一个区域,或者代码块
Lock 不支持锁多次,解多次

# 格式

import threading
R=threading.Lock()
R.acquire()
'''
对公共数据的操作
'''
R.release()

同步锁的引用

from threading import Thread, Lock
import os, time


def work():
    global n
    lock.acquire()
    temp = n
    time.sleep(0.1)
    n = temp - 1
    lock.release()


if __name__ == '__main__':
    lock = Lock()
    n = 100
    l = []
    for i in range(100):
        p = Thread(target=work)
        l.append(p)
        p.start()
    for p in l:
        p.join()

    print(n)  # 结果肯定为0,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全

互斥锁与join的区别

# 不加锁:并发执行,速度快,数据不安全
from threading import current_thread, Thread, Lock
import os, time


def task():
    global n
    print('%s is running' % current_thread().getName())
    temp = n
    time.sleep(0.5)
    n = temp - 1


if __name__ == '__main__':
    n = 100
    lock = Lock()
    threads = []
    start_time = time.time()
    for i in range(100):
        t = Thread(target=task)
        threads.append(t)
        t.start()
    for t in threads:
        t.join()

    stop_time = time.time()
    print('主:%s n:%s' % (stop_time - start_time, n))

'''
Thread-1 is running
Thread-2 is running
......
Thread-100 is running
主:0.5216062068939209 n:99
'''

# 不加锁:未加锁部分并发执行,加锁部分串行执行,速度慢,数据安全
from threading import current_thread, Thread, Lock
import os, time


def task():
    # 未加锁的代码并发运行
    time.sleep(3)
    print('%s start to run' % current_thread().getName())
    global n
    # 加锁的代码串行运行
    lock.acquire()
    temp = n
    time.sleep(0.5)
    n = temp - 1
    lock.release()


if __name__ == '__main__':
    n = 100
    lock = Lock()
    threads = []
    start_time = time.time()
    for i in range(100):
        t = Thread(target=task)
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    stop_time = time.time()
    print('主:%s n:%s' % (stop_time - start_time, n))

'''
Thread-1 is running
Thread-2 is running
......
Thread-100 is running
主:53.294203758239746 n:0
'''

# 既然加锁会让运行变成串行,那么我在start之后立即使用join,
#--就不用加锁了啊,也是串行的效果啊?

# 没错:在start之后立刻使用jion,肯定会将100个任务的执行变成串行,毫无疑问,
#--最终n的结果也肯定是0,是安全的,但问题是:
# start后立即join:任务内的所有代码都是串行执行的,而加锁,
# --只是加锁的部分即修改共享数据的部分是串行的
# 单从保证数据安全方面,二者都可以实现,但很明显是加锁的效率更高.

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


def task():
    time.sleep(3)
    print('%s start to run' % current_thread().getName())
    global n
    temp = n
    time.sleep(0.5)
    n = temp - 1


if __name__ == '__main__':
    n = 100
    lock = Lock()
    start_time = time.time()
    for i in range(100):
        t = Thread(target=task)
        t.start()
        t.join()
    stop_time = time.time()
    print('主:%s n:%s' % (stop_time - start_time, n))

'''
Thread-1 start to run
Thread-2 start to run
......
Thread-100 start to run
主:350.6937336921692 n:0 #耗时是多么的恐怖
'''
死锁与递归锁
死锁

python 创建线程池 销毁线程池 python 线程结束_python 创建线程池 销毁线程池_13

# 死锁

from threading import Lock as Lock
import time

mutexA = Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()

python 创建线程池 销毁线程池 python 线程结束_python 创建线程池 销毁线程池_14

递归锁

一次放一个,支持锁多次,其他都和Lock一样

# 递归锁

from threading import RLock as Lock
import time

mutexA = Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()
# 递归锁 解决上面 打印数字示例
import time
import threading

lock = threading.RLock()

n = 10


def task(i):
    lock.acquire()  # 加锁,此区域的代码同一时刻只能有一个线程执行
    global n
    print('当前线程', i, '读取到的n值为:', n)
    n = i
    time.sleep(1)  # 再复杂也只能一个一个完成,再切换
    print('当前线程', i, '修改n值为:', n)
    lock.release()  # 释放锁


for i in range(10):
    t = threading.Thread(target=task, args=(i,))
    t.start()
    
'''
当前线程 0 读取到的n值为: 10
当前线程 0 修改n值为: 0
当前线程 1 读取到的n值为: 0
当前线程 1 修改n值为: 1
当前线程 2 读取到的n值为: 1
当前线程 2 修改n值为: 2
当前线程 3 读取到的n值为: 2
当前线程 3 修改n值为: 3
当前线程 4 读取到的n值为: 3
当前线程 4 修改n值为: 4
当前线程 5 读取到的n值为: 4
当前线程 5 修改n值为: 5
当前线程 6 读取到的n值为: 5
当前线程 6 修改n值为: 6
当前线程 7 读取到的n值为: 6
当前线程 7 修改n值为: 7
当前线程 8 读取到的n值为: 7
当前线程 8 修改n值为: 8
当前线程 9 读取到的n值为: 8
当前线程 9 修改n值为: 9
'''

死锁示例和解决

# 死锁问题

import time
from threading import Thread, Lock

noodle_lock = Lock()
fork_lock = Lock()


def eat1(name):
    noodle_lock.acquire()
    print('%s 抢到了面条' % name)
    fork_lock.acquire()
    print('%s 抢到了叉子' % name)
    print('%s 吃面' % name)
    fork_lock.release()
    noodle_lock.release()


def eat2(name):
    fork_lock.acquire()
    print('%s 抢到了叉子' % name)
    time.sleep(1)
    noodle_lock.acquire()
    print('%s 抢到了面条' % name)
    print('%s 吃面' % name)
    noodle_lock.release()
    fork_lock.release()


for name in ['jaychou', 'eason', 'jj']:
    t1 = Thread(target=eat1, args=(name,))
    t2 = Thread(target=eat2, args=(name,))
    t1.start()
    t2.start()
'''
jaychou 抢到了面条
jaychou 抢到了叉子
jaychou 吃面
jaychou 抢到了叉子
eason 抢到了面条
'''
# 递归锁解决死锁问题

import time
from threading import Thread, RLock

fork_lock = noodle_lock = RLock()


def eat1(name):
    noodle_lock.acquire()
    print('%s 抢到了面条' % name)
    fork_lock.acquire()
    print('%s 抢到了叉子' % name)
    print('%s 吃面' % name)
    fork_lock.release()
    noodle_lock.release()


def eat2(name):
    fork_lock.acquire()
    print('%s 抢到了叉子' % name)
    time.sleep(1)
    noodle_lock.acquire()
    print('%s 抢到了面条' % name)
    print('%s 吃面' % name)
    noodle_lock.release()
    fork_lock.release()


for name in ['jaychou', 'eason', 'jj']:
    t1 = Thread(target=eat1, args=(name,))
    t2 = Thread(target=eat2, args=(name,))
    t1.start()
    t2.start()
    
'''
jaychou 抢到了面条
jaychou 抢到了叉子
jaychou 吃面
jaychou 抢到了叉子
jaychou 抢到了面条
jaychou 吃面
eason 抢到了面条
eason 抢到了叉子
eason 吃面
eason 抢到了叉子
eason 抢到了面条
eason 吃面
jj 抢到了面条
jj 抢到了叉子
jj 吃面
jj 抢到了叉子
jj 抢到了面条
jj 吃面
'''
BoundedSemaphore 信号量

一次放N个(固定个数),参数代表个数

import time
import threading

lock = threading.BoundedSemaphore(3)


def func(arg):
    lock.acquire()
    time.sleep(1)
    print(arg)
    lock.release()


for i in range(20):
    t = threading.Thread(target=func, args=(i,))
    t.start()
Condition

放行用户动态输入的个数

# 方式一

import time
import threading

lock = threading.Condition()


def func(arg):
    print('线程进来了')
    lock.acquire()
    lock.wait()  # 加锁

    print(arg)
    time.sleep(1)

    lock.release()


for i in range(10):
    t = threading.Thread(target=func, args=(i,))
    t.start()

while True:  # 主线程
    inp = int(input('>>>'))

    lock.acquire()
    lock.notify(inp)  # 写放行的线程个数
    lock.release()
# 方式二

import time
import threading

lock = threading.Condition()


def xxxx():  # 限制条件内容
    print('来执行函数了')
    input(">>>")
    # ct = threading.current_thread() # 获取当前线程
    # ct.getName()
    return True


def func(arg):
    print('线程进来了')
    lock.wait_for(xxxx)  # 参数为限制条件,得到true则放行
    print(arg)
    time.sleep(1)


for i in range(10):
    t = threading.Thread(target=func, args=(i,))
    t.start()
Timer和Event

Timer
Timer继承子Thread类,是Thread的子类,也是线程类,具有线程的能力和特征。
这个类用来定义多久执行一个函数。它的实例是能够延迟执行目标函数的线程,
在真正执行目标函数之前,都可以cancel它

# Timer源码

class Timer(Thread):
    def __init__(self, interval, function, args=None, kwargs=None):
        Thread.__init__(self)
        self.interval = interval
        self.function = function
        self.args = args if args is not None else []
        self.kwargs = kwargs if kwargs is not None else {}
        self.finished = Event()
    def cancel(self):
        """Stop the timer if it hasn't finished yet."""
        self.finished.set()
 
    def run(self):
        self.finished.wait(self.interval)
        if not self.finished.is_set():
            self.function(*self.args, **self.kwargs)
        self.finished.set()

Timer类使用方法与Thread定义子线程一样,interval传入间隔时间,
function传入线程执行的函数,args和kwargs传入函数的参数

# 提前cancel
import threading
import time
 
def add(x,y):
    print(x+y)
 
t = threading.Timer(10,add,args=(4,5))
t.start()
 
time.sleep(2)
t.cancel()
print("===end===")
 
# 运行结果:
# ===end===
'''
start方法执行之后,Timer对象会处于等待状态,等待10秒之后会执行add函数。
同时,在执行add函数之前的等待阶段,主线程使用了子线程的cancel方法,
就会跳过执行函数结束!
'''
# 使用event 事件实现Timer计时器

import threading
import logging
import time
logging.basicConfig(level=logging.INFO)
 
# class MyTimer(threading.Thread):
class MyTimer:
    def __init__(self,interval,fn,args=None):
        self.interval = interval
        self.fn = fn
        self.args = args
        self.event = threading.Event()
 
    def start(self):
        threading.Thread(target=self.__do).start()
 
    def cancel(self):
        self.event.set()
 
    def __do(self):
        self.event.wait(self.interval)
        if not self.event.is_set():
            self.fn(*self.args)
 
 
def add(x,y):
    logging.warning(x+y)
 
 
t = MyTimer(5,add,(4,5))
t.start()
 
# time.sleep(2)
# t.cancel()
 
# 运行结果:
# WARNING:root:9

Event 一次放所有
Event事件,是线程间通信机制中最简单的实现,使用一个内部的标记flag,
通过flag的True或False的变化来进行操作

# Event 源码
class Event:
 
    def __init__(self):
        self._cond = Condition(Lock())
        self._flag = False
 
    def _reset_internal_locks(self):
        self._cond.__init__(Lock())
 
    def is_set(self):
        return self._flag
 
    isSet = is_set
 
    def set(self):
        with self._cond:
            self._flag = True
            self._cond.notify_all()
 
    def clear(self):
        with self._cond:
            self._flag = False
 
    def wait(self, timeout=None):
        with self._cond:
            signaled = self._flag
            if not signaled:
                signaled = self._cond.wait(timeout)
            return signaled

python 创建线程池 销毁线程池 python 线程结束_多线程相关_15

import time
import threading

lock = threading.Event()


def func(arg):
    print('线程来了')
    lock.wait()  # 加锁:红灯 执行第一次set后变成绿灯了
    print(arg)


for i in range(10):
    t = threading.Thread(target=func, args=(i,))
    t.start()

input(">>>>")
lock.set()  # 绿灯

lock.clear()  # 再次变红灯

for i in range(10):
    t = threading.Thread(target=func, args=(i,))
    t.start()

input(">>>>")
lock.set()

实例
老板雇佣了一个工人,让他生产杯子,老板一直等着工人,直到生产了10个杯子

import threading
import logging
import time

logging.basicConfig(level=logging.INFO)

cups = []
event = threading.Event()  # event对象


def boss(e: threading.Event):
    if e.wait(30):  # 最多等待30秒
        logging.info('Good job.')


def worker(n, e: threading.Event):
    while True:
        time.sleep(0.5)
        cups.append(1)
        logging.info('make 1')
        if len(cups) >= n:
            logging.info('I finished my job. {}'.format(len(cups)))
            e.set()  # flag设置为True
            break


b = threading.Thread(target=boss, name='boos', args=(event,))
w = threading.Thread(target=worker, args=(10, event))

w.start()
b.start()

'''
运行结果:
INFO:root:make 1
INFO:root:make 1
INFO:root:make 1
INFO:root:make 1
INFO:root:make 1
INFO:root:make 1
INFO:root:make 1
INFO:root:make 1
INFO:root:make 1
INFO:root:make 1
INFO:root:I finished my job. 10
INFO:root:Good job.
'''

老板和工人使用同一个Event对象的标记flag;
老板wait()设置为最多等待30秒,等待flag变为True,工人在做够10杯子时,
将flag设置为True,工人必须在30秒之内完成没有做好杯子。

wait 的使用

import threading
import logging
logging.basicConfig(level=logging.INFO)
 
def do(event:threading.Event,interval:int):
    while not event.wait(interval): # not event.wait(1) = True
        logging.info('To do sth.')
 
e = threading.Event()
t = threading.Thread(target=do,args=(e,1))
t.start()
 
e.wait(10) # 也可以使用time.sleep(10)
e.set()
print('Man Exit.')

'''
运行结果:
INFO:root:To do sth.
INFO:root:To do sth.
INFO:root:To do sth.
INFO:root:To do sth.
INFO:root:To do sth.
INFO:root:To do sth.
INFO:root:To do sth.
INFO:root:To do sth.
INFO:root:To do sth.
Man Exit.
'''
# wait与sleep的区别是:wait会主动让出时间片,其它线程可以被调度,
# --而sleep会占用时间片不让出

Timer定时器继承自Thread类,也是线程类。
它的作用是等待n秒钟之后执行某个目标函数,可以使用cancel提前取消。
Event事件是通过True和False维护一个flag标记值,
通过这个标记的值来决定做某事
wait()方法可以设置最长等待flag设置为Ture的时长;
超时还未设置为True就返回False

线程队列

queue队列 :使用import queue,用法与进程Queue一样
queue is especially useful in threaded programming when information
must be exchanged safely between multiple threads.

# class queue.Queue(maxsize=0) # 先进先出
import queue

q=queue.Queue()
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())
'''
结果(先进先出):
first
second
third
'''

class queue.LifoQueue(maxsize=0) # 先进后出

# 后进先出 
import queue

q=queue.LifoQueue()
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())
'''
结果(后进先出):
third
second
first
'''

class queue.PriorityQueue(maxsize=0) # 存储数据时可设置优先级的队列

# 优先级队列
import queue

q = queue.PriorityQueue()
# put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
q.put((20, 'a'))
q.put((10, 'b'))
q.put((30, 'c'))

print(q.get())
print(q.get())
print(q.get())
'''
结果(数字越小优先级越高,优先级高的优先出队):
(10, 'b')
(20, 'a')
(30, 'c')
'''

Python标准模块:concurrent.futures

'''
# 1 介绍
concurrent.futures模块提供了高度封装的异步调用接口
ThreadPoolExecutor:线程池,提供异步调用
ProcessPoolExecutor: 进程池,提供异步调用
Both implement the same interface, which is defined by the abstract Executor class.

# 2 基本方法
# submit(fn, *args, **kwargs)
异步提交任务

# map(func, *iterables, timeout=None, chunksize=1) 
取代for循环submit的操作

# shutdown(wait=True) 
相当于进程池的pool.close()+pool.join()操作
wait=True,等待池内所有任务执行完毕回收完资源后才继续
wait=False,立即返回,并不会等待池内的任务执行完毕
但不管wait参数为何值,整个程序都会等到所有任务执行完毕
submit和map必须在shutdown之前

# result(timeout=None)
取得结果

# add_done_callback(fn)
回调函数

# done()
判断某一个线程是否完成

# cancle()
取消某个任务
'''
ThreadPoolExecutor 线程池

作用:限制最多可以开辟线程的个数

from concurrent.futures import ThreadPoolExecutor
import time


def task(a1, a2):
    time.sleep(2)
    print(a1, a2)


# 创建了一个线程池(最多5个线程)
pool = ThreadPoolExecutor(5)

for i in range(40):
    # 去线程池中申请一个线程,让线程执行task函数。
    pool.submit(task, i, 8)
# ######################## 无限制线程,避免这样做 ###########################
import time
import threading


def task(arg):
    time.sleep(50)


while True:
    num = input('>>>')
    t = threading.Thread(target=task, args=(num,))
    t.start()

######################## 线程池 (正确做法)###########################
import time
from concurrent.futures import ThreadPoolExecutor


def task(arg):
    time.sleep(50)


pool = ThreadPoolExecutor(20)
while True:
    num = input('>>>')
    pool.submit(task, num)
线程池用法–爬虫实例
安装模块
'''
控制台安装:
    pip3 install requests 
    pip3 install beautifulsoup4
'''

pip3 install 遇到的问题

'''
在python中Scripts目录下找到pip3
     # Scripts:脚本(相当于一个仓库)

找不到内部指令?
方式一:控制台写上完整的路径
	python安装路径\Scripts\pip3 install requests 
方式二:在环境变量加上完整路径
	C:\Users\Administrator\AppData\Local\Programs\Python\Python36\Scripts

版本低?
You are using pip version 10.0.1, however version 19.2.3 is available.
You should consider upgrading via the 'python -m pip install--upgrade pip' command.

确定是python安装目录后,在控制台直接输入提示升级命令:python -m pip install --upgrade 
'''
# Python\Scripts\python -m pip install --upgrade 后,提示成功,则升级成功了pip

python 创建线程池 销毁线程池 python 线程结束_多线程_16

'''
pycharm上安装模块的方法:

pycharm->file->setting->project:
	当前项目->project interpreter->右边“+”->搜索requests->install package
'''
爬虫示例
'''
浏览器发送请求:
“ 检查>Network>找到网址>request-headers>user-agent ”
User-Agent:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) 
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36 
  # 能看到浏览器信息
'''
# 实现 

import requests
from bs4 import BeautifulSoup  # 用来解析网页内容
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor


# 模拟浏览器发送请求
# 内部创建 sk = socket.socket()
# 和抽屉进行socket连接 sk.connect(...)
# sk.sendall('...')
# sk.recv(...)

def task(url):
    # print(url)
    r1 = requests.get(
        url=url,
        headers={
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36'
        }
    )

    # 查看下载下来的文本信息
    soup = BeautifulSoup(r1.text, 'html.parser')
    content_list = soup.find('div', attrs={'id': 'content-list'})
    for item in content_list.find_all('div', attrs={'class': 'item'}):
        title = item.find('a').text.strip()
        target_url = item.find('a').get('href')
        print(title, target_url)


def run():
    pool = ThreadPoolExecutor(5)
    for i in range(1, 50):
        pool.submit(task, 'https://dig.chouti.com/all/hot/recent/%s' % i)


if __name__ == '__main__':
    run()
map的用法
# map的用法

from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor

import os, time, random


def task(n):
    print('%s is runing' % os.getpid())
    time.sleep(random.randint(1, 3))
    return n ** 2


if __name__ == '__main__':
    executor = ThreadPoolExecutor(max_workers=3)

    # for i in range(11):
    #     future=executor.submit(task,i)

    executor.map(task, range(1, 12))  # map取代了for+submit
回调函数

通俗理解:回调函数就是一个参数,将这个函数作为参数传到另一个函数里面,
当那个函数执行完之后,再执行传进去的这个函数。这个过程就叫做回调

# 简单示例

def main_func(callback):
    print("main function run")
    callback()


def call_func():
    print("callback function run")


main_func(call_func)
'''
main function run
callback function run

主函数运行完,参数传入的函数自己再执行,这个过程就是回调
'''

实例

from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
from multiprocessing import Pool
import requests
import json
import os


def get_page(url):
    print('<进程%s> get %s' % (os.getpid(), url))
    respone = requests.get(url)
    if respone.status_code == 200:
        return {'url': url, 'text': respone.text}


def parse_page(res):
    res = res.result()
    print('<进程%s> parse %s' % (os.getpid(), res['url']))
    parse_res = 'url:<%s> size:[%s]\n' % (res['url'], len(res['text']))
    with open('db.txt', 'a') as f:
        f.write(parse_res)


if __name__ == '__main__':
    urls = [
        'https://www.baidu.com',
        'https://www.python.org',
        'https://www.openstack.org',
        'https://help.github.com/',
        'http://www.sina.com.cn/'
    ]

    # p=Pool(3)
    # for url in urls:
    #     p.apply_async(get_page,args=(url,),callback=pasrse_page)
    # p.close()
    # p.join()

    p = ProcessPoolExecutor(3)
    for url in urls:
        p.submit(get_page, url).add_done_callback(parse_page)  
        # parse_page拿到的是一个future对象obj,需要用obj.result()拿到结果

练习题

  • 有一个文件,这个文件中有20001行数据,开启一个线程池,为每100行创建一个任务,打印这100行数据
# 1. print 和 文件的读 写 都是io操作,
# 这个题主要就是起线程在一个线程读文件的时候,利用这个线程读取文件的时间交给另一个线程来进行打印操作
# 2. 使用线程可以有效的规避掉io操作的时间,提高程序的的效率
# 3.解耦程序的功能
# 4.默认参数是列表的运用

# 写法一:
from concurrent.futures import ThreadPoolExecutor

def print_line(lines):
    print(lines)
    
tp = ThreadPoolExecutor(200)
with open('test.txt', encoding='utf-8') as f:
    lines = []
    for line in f:
        if len(lines) == 100:
            tp.submit(print_line, lines)
            lines.clear()
        lines.append(line)
    if lines: # 多出的数据
        tp.submit(print_line, lines)
# 写法二:优化
from concurrent.futures import ThreadPoolExecutor
def print_line(lines):
    print(lines)

def read_file(filename):
    with open(filename, encoding='utf-8') as f:
        for line in f:
            yield line

def submit_func(tp,line=None,end= False,lines = []):
    if line:
        lines.append(line)
    if len(lines) == 100 or end:
        tp.submit(print_line, lines)
        lines.clear()

tp = ThreadPoolExecutor(20)
for line in read_file('file'):
    submit_func(tp,line)
submit_func(tp,end=True)