Python并发编程

并发

并发和并行的区别
  • 并行:同时做某些事,可以互不干扰的同一个时刻做几件事(处理事情的能力)
  • 并发:也是同时做某些事,但是强调,一个时段内有事情要处理(需要处理的事情)
并发的解决
  • 队列,缓冲区:先进先出,解决了资源使用的问题。排成的队列,其实就是一个缓冲地带,就是缓冲区
  • 争抢:争抢也是一种高并发解决方案,但是,这样可能不好,因为有可能有些程序很长时间抢不到资源,(谁抢到资源就上锁,排他性的锁,这是一种锁机制)
  • 预处理:一种提前加载用户需要的数据的思路,预处理思想,缓存常用。
  • 并行:多进程、多线程实现并行处理,来解决并发问题。注意这些都是水平扩展思想(增加硬件措施,成本提高)
  • 提速:提高单个CPU性能,或单个服务器安装更多的CPU。这是一种垂直扩展思想
  • 消息中间件:类似于缓冲,常见的消息中间件有RabbitMQ、ActiveMQ(Apache)、RocketMQ(阿里Apache)、kafka(Apache)等

进程与线程
  • 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础
  • 进程和程序的关系:程序是源代码编译后的文件,而这些文件存放在磁盘上。当程序被操作系统加载到内存中,就是进程,进程中存放着指令和数据(资源),它也是线程的容器
  • 代操作系统提出进程的概念,每一个进程都认为自己独占所有的计算机硬件资源。
  • 线程:是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC)、寄存器集合和堆栈组成
线程的状态
状态含义
就绪(Ready)线程能够运行,但在等待被调度。可能线程刚刚创建启动,或刚刚从阻塞中恢复,或者被其他线程抢占
运行(Running)线程正在运
行阻塞(Blocked)线程等待外部事件发生而无法运行,如I/O操作
终止(Terminated)线程完成,或退出,或被取消

2019-6-5 9-44-51.png

python的线程开发

python的线程开发使用标准库 threading
进程靠线程执行代码,至少有一个主线程,其他线程就是工作线程
主线程是第一个启动的线程

Thread 类
# 签名
def __init__(self,group=None,target=None,name=None,args=(),kwargs=None,*,daemon=None):
    
参数名含义
target线程调用的对象,就是目标函数
name为线程起个名字
args为目标函数传递实参,元组
kwargs为目标函数关键字传参,字典
线程启动
import threading

def worker():
    print('working')
    print('Fineshed')

t = threading.Thread(target=worker,name='worker')  # 创建线程对象
t.start()  # 启动线程
  • 通过threading.Thread创建一个线程对象,target是目标函数,可以使用name为线程指定名称。但是线程没有启动,需要调用start方法。
  • 线程之所以执行函数,是因为线程中就是要执行代码的,而最简单的封装就是函数,所以还是函数调用。函数执行完,线程也就退出了
线程退出
  • python没有提供线程退出的方法,线程在项目情况时退出
    • 线程函数内语句执行完毕
    • 线程函数中抛出未处理的异常
      Python的线程没有优先级、没有线程组的概念,也不能被销毁、停止、挂起,那也就没有恢复、中断了。
线程的传参
import threading
import time

def add(x:int,y:int):
    print('{}+{}={},{}'.format(x,y,x+y,threading.current_thread().ident))


t1 = threading.Thread(target=add,name='add',args=(1,2))  #创建线程对象
t1.start()  # 启动线程

time.sleep(2)
t2 = threading.Thread(target=add,name='add',args=(10,),kwargs={'y':8})
t2.start()

time.sleep(1)
t3 = threading.Thread(target=add,name='add',kwargs={'x':20,'y':8})
t3.start()
-----------------------------------------------------------------
1+2=3,18404
10+8=18,8252
20+8=28,9672

线程传参和函数传参没有区别,本质上就是函数传参

threading的属性和方法
名称含义
current_thread()返回当前线程对象
main_thread()返回主线程对象
active_count()当前处于alive状态的线程个数
enumerate()返回所有活着的线程的列表,不包括已经终止的线程和未开始的线程
get_ident()返回当前线程的ID,非0整数
  • active_count、enumerate方法返回的值还包括主线程
import threading
import time

def showthreadinfo():
    print('current thread={},main thread={},active thread={},current active thread.ident={}'.format(threading.current_thread(),\
        threading.main_thread(),threading.active_count(),threading.current_thread().ident))

time.sleep(1)
t3 = threading.Thread(target=showthreadinfo(),name='showthreadinfo')
t3.start()
-----------------------------------------------------------------------
current thread=<_MainThread(MainThread, started 3524)>,main thread=<_MainThread(MainThread, started 3524)>,active thread=1,current active thread.ident=3524
Thread实例的属性和方法
名称含义
name只是一个名字,只是个标识,名称可以重复,getName(),setName()获取,设置这个名词
ident线程Id,它是非0 整数,线程启动后才会有ID,否则None,线程退出,此ID依旧可以访问
is_alive()返回线程是否活着

注意:线程的name这是一个名称,可以重复;ID必须唯一,但可以在线程退出后再利用。


名称含义
start()启动线程,每一个线程必须且只能执行该方法一次
run()运行线程函数
start()方法,run()方法和区别
import threading
import time

def worker():
    print(threading.enumerater())  # 通过enumerate()查看线程列表
    for i in range(2):
        time.sleep(1)
        print('i am working')
    print('working finished')

class myThread(threading.Thread):    # myThread 继承了threading 里的Thread类
    def start(self):
        print('start---')
        super().start()

    def run(self):
        print('run---')
        super().run()               # 引用了父类里的run函数

t = myThread(target=worker,name='worker')
t.start()
----------------------------------------------------------------------
start---
run---
[<_MainThread(MainThread, started 15936)>, <myThread(worker, started 17300)>]
i am working
i am working
working finished
run()方法
import threading
import time

def worker():
    print(threading.enumerater())  # 通过enumerate()查看线程列表
    for i in range(2):
        time.sleep(1)
        print('i am working')
    print('working finished')

class myThread(threading.Thread):    # myThread 继承了threading 里的Thread类
    def start(self):
        print('start---')
        super().start()

    def run(self):
        print('run---')
        super().run()               # 引用了父类里的run函数

t = myThread(target=worker,name='worker')
t.run()
----------------------------------------------------------------------
run---
i am working
i am working
working finished
  • start()方法会调用run()方法,而run()方法可以运行函数
  • run()方法和start()方法的区别在于:
    使用strat方法启动新线程,但是使用run()方法并没有启动新线程,只是在主线程中调用了一个函数而已,因此启动线程需要使用start方法,而且start方法只能调用一次.

多线程

一个进程中如果有多个线程,就是多线程,实现一种并发

import threading
import time

def worker():
    print(threading.enumerate())  # 通过enumerate()查看线程列表
    for i in range(2):
        time.sleep(1)
        print('i am working')
    print('working finished')

class myThread(threading.Thread):    # myThread 继承了threading 里的Thread类
    def start(self):
        print('start---')
        super().start()

    def run(self):
        print('run---')
        super().run()               # 引用了父类里的run函数

t1 = myThread(target=worker,name='worker1')
t1.start()                                   # 启动第一个线程
t2 = myThread(target=worker,name='worker2')
t2.start()                                   # 启动第二个线程
-----------------------------------------------------------------------
start---  
run---
[<_MainThread(MainThread, started 3720)>, <myThread(worker1, started 16024)>]
start---
run---
[<_MainThread(MainThread, started 3720)>, <myThread(worker1, started 16024)>, <myThread(worker2, started 1396)>]
i am working
i am working
i am working
working finished
i am working
working finished
  • 使用start方法启动线程后,进程内有多个活动的线程并行的工作,就是多线程
  • 一个进程中至少有一个线程,并作为程序的入口,这个线程就是主线程。一个进程至少有一个主线程。其他线程称为工作线程

  • 如下:所有线程的目标只有一个,就是要count递增到100,一旦一个线程递增到100,其他线程就不应该再递增才是我们需要的程序,(换种思维,所有工作线程都是帮忙主线程完成目标,一旦有一个工作线程完成目标,其余线程不再工作,只走过场而已)
import threading
import time

count = []
def worker(n):
    while len(count) < n:
        time.sleep(0.001)
        count.append(1)
    print('over',len(count),threading.current_thread().name)

for i in range(5):                         
    print('start{}'.format(1+i))
    threading.Thread(target=worker,name='worker{}'.format(1+i),args=(100,)).start()
--------------------------------------------------------------------------------------
start1
start2
start3
start4
start5
over 100 worker3
over 101 worker4
over 102 worker5
over 103 worker1
over 104 worker2
#基本上是同时启动5个线程,但在count还差1是,5个线程同时递增1,就到了104,这样不是我们所需要的代码

解决上面的问题,用锁

import threading
import time

lock = threading.Lock()
count = []
def worker(n):
    a = 0
    flag = False
    while True:
        lock.acquire()
        if len(count) >= n:
            flag = True
        if not flag:
            time.sleep(0.001)
            a += 1
            count.append(1)
        lock.release()
        if flag:
            break
    print('over', len(count), threading.current_thread().name, a)


for i in range(5):
    print('start{}'.format(1+i))
    threading.Thread(target=worker,name='worker{}'.format(1+i),args=(100,)).start()
--------------------------------------------------------------------------------------
start1
start2
start3
start4
start5
over 100 worker5 28
over 100 worker2 67
over 100 worker3 0         
over 100 worker4 0
over 100 worker1 5
# worker3和worker4线程并没有参与计算,一直被阻塞到目标完成,最后什么事都没有干,走了个过场而已

线程的安全

import threading
def worker():    
    for x in range(5):
        print("{} is running.".format(threading.current_thread().name))
        
for x in range(1, 8): # 可以增加线程数    
    name = "worker{}".format(x) 
    t = threading.Thread(name=name, target=worker)    
    t.start()
------------------------------------------------------------
worker1 is running.worker2 is running.
worker2 is running.
worker2 is running.
...
  • 从输出结果来看,print函数被切换打断了,print函数分两步,第一步打印字符串,第二步打印换行符,就在这之间,发生了线程的切换,这说明print函数的线程不安全的

不让print打印换行,字符串是不可变的类型,它可以作为一个整体不可分割输出。end=’'就不在让print输出换行了

使用logging

标准库里面的logging模块,日志处理模块,线程安全的,生成环境代码都使用logging

import threading
import logging

def worker():    
    for x in range(5):
        logging.warning('{} is runing'.format(threading.current_thread()))
#         print("{} is running.".format(threading.current_thread().name),end='')
        
for x in range(1, 8): # 可以增加线程数    
    name = "worker{}".format(x) 
    t = threading.Thread(name=name, target=worker)    
    t.start()
------------------------------------------------------------
WARNING:root:<Thread(worker1, started 2320)> is runing
WARNING:root:<Thread(worker2, started 14740)> is runing
WARNING:root:<Thread(worker3, started 11444)> is runing
......

daemon线程和non-daemon线程

# 源码中定义的daemon,默认为None
def __init__(self, group=None, target=None, name=None,
                 args=(), kwargs=None, *, daemon=None):

线程daemon属性,如果设定就是用户的设置,否则就取当前线程daemon的值,主线程是non-daemon线程,即daemon=False

源码如下

def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None):
    if daemon is not None:
        self._daemonic = daemon
    else:
        self._daemonic = current_thread().daemon
import threading
import time

def worker():
    print(threading.enumerate())  
    for i in range(2):
        time.sleep(1)
        print('i am working')
    print('working finished')

class myThread(threading.Thread):  
    def start(self):
        print('start---')
        super().start()

    def run(self):
        print('run---')
        super().run()               

t1 = myThread(target=worker,name='worker1',daemon=True)   #daemon值改为True,则主线程不等工作线程结束,主线程就结束了
t1.start()
print('module finished')
-----------------------------------------------
start---
run---
[<_MainThread(MainThread, started 8792)>, <myThread(worker1, started daemon 16572)>]
module finished    #结果看出,主线程快于工作线程结束 
名称含义
daemon属性表示线程是否是daemon线程,这个值必须在start()之前设置,否则引发RuntimeError异常
isDaemon()是否是daemon线程
setDaemon设置为daemon线程,必须在start方法之前设置
import threading
import time

def worker(name, timeout):
    time.sleep(timeout)
    print('{} working'.format(name))

t = threading.Thread(target=worker, args=('t1', 1), daemon=False)
t.start()  # t1 打印执行,因为t1为non-daemon线程,主线程会等t1线程结束后才会结束

t2 = threading.Thread(target=worker, args=('t2', 3), daemon=True)   
t2.start()   # t2 并未打印执行,因为t2为daemon线程,主线程不会等t2线程
print('main thread exits')
-----------------------------------------------
main thread exits
t1 working
  • 总结:
    • 线程具有一个daemon属性,可以手动设置为True或False,也可以不设置,则取默认值None。
    • 如果不设置daemon,就取当前线程的daemon来设置它。
    • 主线程是non-daemon线程,即daemon = False。
    • 从主线程创建的所有线程的不设置daemon属性,则默认都是daemon = False,也就是non-daemon线程
    • Python程序在没有活着的non-daemon线程运行时,程序退出,也就是除主线程之外剩下的只能都是daemon线程,主线程才能退出,否则主线程就只能等待。
jion方法
  • daemon线程使用join方法后daemon线程执行完了,主线程才退出
import threading
import time

def worker():
    print(threading.enumerate())  # 通过enumerate()查看线程列表
    for i in range(2):
        time.sleep(1)
        print('i am working')
    print('working finished')

class myThread(threading.Thread):    # myThread 继承了threading 里的Thread类
    def start(self):
        print('start---')
        super().start()

    def run(self):
        print('run---')
        super().run()               # 引用了父类里的run函数

t1 = myThread(target=worker,name='worker1',daemon=True)
t1.start()
t1.join(5)
print('---------------------------------')
print('module finished')
----------------------------------------------------------------------
start---
run---
[<_MainThread(MainThread, started 4248)>, <myThread(worker1, started 508)>]
i am working
i am working
working finished
---------------------------------
module finished    #使用join方法后daemon线程执行完了,主线程才退出(t1线程耗时在2秒左右,主线程被阻塞了5秒,工作线程快于主线程结束)

  • join(timeout=None),是线程的标准方法之一。
  • 一个线程中调用另一个线程的join方法,调用者将被阻塞,直到被调用线程终止
  • 一个线程可以被join多次。
  • timeout参数指定调用者等待多久,没有设置超时,就一直等到被调用线程结束。调用谁的join方法,就是join谁,就要等谁

threading.local类

  • python提供threading.local类,将这个类实例化得到一个全局对象,但是不同的线程使用这个对象存储的数据其他线程看不见(全局对象和局部变量效果一样)
#用threading.local类
import threading
import time

# 全局对象
global_data = threading.local()

def worker():    
    global_data.x = 0    
    for i in range(100):
        time.sleep(0.0001)
        global_data.x += 1    
    print(threading.current_thread(), global_data.x)
        
for i in range(10):    
    threading.Thread(target=worker).start()
--------------------------------------------------------
<Thread(Thread-349, started 7928)><Thread(Thread-351, started 17588)> 100
<Thread(Thread-348, started 5784)> 100
<Thread(Thread-352, started 10920)> 100
<Thread(Thread-350, started 8856)> 100
 100
# 局部变量
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-345, started 11968)> 100
<Thread(Thread-344, started 1280)> 100
<Thread(Thread-343, started 17272)> 100
<Thread(Thread-346, started 18288)> 100
<Thread(Thread-347, started 17928)> 100

import threading

global_data = threading.local()
global_data.x = 100

def worker():    
    print(global_data)
    print(global_data.x)
        
threading.Thread(target=worker).start()
-------------------------------------------------------
<_thread._local object at 0x000001B37CB90E08>
Exception in thread Thread-353:
...
AttributeError: '_thread._local' object has no attribute 'x'

以上代码运行结果可以看出

  • 另起一个线程打印 global_data.x 出错了。AttributeError: ‘_thread._local’ object has no attribute 'x’但是,global_data打印没有出错,说明看到global_data,但是global_data中的x看不到,这个x不能跨线程。

  • threading.local类构建了一个大字典,存放所有线程相关的字典,定义如下:{ id(Thread) -> (ref(Thread), thread-local dict) }每一线程实例的id为key,元组为value。value中2部分为,线程对象引用,每个线程自己的字典。

  • 本质:
    运行时,threading.local实例处在不同的线程中,就从大字典中找到当前线程相关键值对中的字典,
    覆盖threading.local实例的__dict__。这样就可以在不同的线程中,安全地使用线程独有的数据,
    做到了线程间数据隔离,如同本地变量一样安全。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值