chapter11.1、并发,进程,线程

并发

并发和并行

并行,parallel,某时刻,同时做几件事

并发,concurrency,时间段内有事情要处理

并发解决方案

并发必须解决,所有程序几乎都要面对,特别是面对用户是互联网用户时,高并发更需要处理

打饭模型,中午十二点,食堂涌入大量人,如果人很多,这就是高并发。

1,队列,排队,

假设资源只有一个,队列或者列表是较好的方式

可以使用优先队列或者双队列

队列就是缓冲区buffer,代价较小,但是在面对高并发时可能会不够用

作用是平滑请求

例如queue模块的类,Queue,LifoQueue,PriorityQueue

2、争抢

挤到窗口的,会一直占据着窗口,可以视为锁定窗口,不在为其他人服务,视为一种锁机制

争抢是一种排他性的锁,其他人只能等。

对于某些请求者,可能永远抢不到资源

有争抢就要有锁

3、预处理

如果排长队的原因,是由于每个人的等候时间太长,就要考虑提前将热门的80%的饭菜做好,保证供应,其他的现做,这样就算锁定窗口,也能很快就释放。

提前加载用户需要的数据的思路,就是预处理思想,缓存常用。

高并发常用解决思想,大规模时使用分布式缓存

4、并行

分配问题,轮询(最简单的模型),要考虑队伍长短的解决方案,可以实时看队列的情况来看放入那个队列。

开窗口就像扩大食堂,会造成成本上升

日常可以通过购买更多服务器,或者开多线程,多进程并行实现

水平扩展思想

线程在单cpu时,就不是并行了

并行只是并发的解决方案之一

5、提速

提高打饭速度,也是解决方案

提高单个CPU性能,或者服务器安装更多cpu

垂直扩展思想

6、消息中间件

地铁站外的安检走廊,缓冲人流,进去后还要重新排队

队列作用是缓冲,解耦

常用第三方队列,也就是消息中间件,常用的有RabbitMQ、ActiveMQ(Apache)、RocketMQ(阿里Apache)、kafka(Apache)等

解决高并发的方法还有很多,一般会根据不同的场景用不同的策略,而策略可以是多种方式的优化组合。

可以多开食堂(多地),也可以就近,建到宿舍附近(就近),技术来源生活。

线程,进程

实现了线程的操作系统中,线程是操作系统能够运算调度的最小单位。线程包含在进程中,是进程的实际运作单位,一个程序的执行实例就是进程。

进程是计算机中程序关于某数据集合的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统的基础。 

  ABI (Application Binary Interface)  应用程序二进制接口

程序源代码编译后的文件存放在磁盘中,当程序被操作系统加载到内存中时,就是进程,进程中存放着指令和数据(资源),进程也是线程的容器

Linux中有父进程,子进程,Windows的进程是平等关系

操作系统必须有进程管理

线程 有时被称为轻量级进程LWP(Lightweight Process)是程序执行流的最小单元

一个标准的线程由线程的ID,当前指令指针(PC:程序计数器),寄存器集合和堆栈组成。

理解为:

进程就是独立的王国,不会共享数据

线程就是省份,同一个进程内的线程可以共享进程的资源,每一个线程有自己的数据,独立的堆栈

线程状态

ready  就绪 线程能够运行,但在等待调度

running  运行 线程正在运行

blocked  阻塞 线程等待外部事件的发生而无法运行,如I/O操作

terminal  终止,线程完成或者退出,或者取消

以上是较常见的几种状态,不同的操作系统可能会不同

线程的状态转换

虚拟化:把一个资源当做多个资源用

分时的想法,将cpu的运算时间细分,时间切分的十分的小,几微秒处理一个线程,让人感觉像是同时在处理。可以加入优先的思想,让人感觉这个处理的较快,这些全部由操作系统调度。这里就利用了虚拟化的思想。

时间片

多核,可以同时执行;单核,基本都是分时的。

Python的线程开发

Python的进程会启动解释器进程,线程共享一个解释器进程

Python线程的开发使用标准库threading

Thread类

#签名
def __init__(self, group=None, target=None, name=None,
                 args=(), kwargs=None, *, daemon=None):

参数:

target  线程调用的函数,就是目标函数

name  线程的名字

args  目标函数的参数,实参,要求必须是元组

kwargs  为目标函数关键字传参,字典

线程目标创建后并不会启动,要使用start方法

线程就是执行代码的,函数是最简单的封装,函数执行完就退出,如果要不退出,就要使用while

import threading,time

def worker(x,y):
    count = 0
    while count < 5:
        print('I am working',x,y)
        time.sleep(2)
        count += 1
    print('finish')

t = threading.Thread(target=worker,name='work1',args=(4,),kwargs={'y':2})

t.start()
print('+++end+++')

线程退出

python没有提供线程退出的方法,结束的方法就是语句执行完或者抛出未处理的异常

python的线程没有优先级,不能被销毁,停止,挂起,也没有恢复,中断

线程的传参和函数的传参相同。

threading的属性方法

current_thread()  返回当前线程对象

main_thread()  返回主线程对象

active_thread()  返回处于alive状态的线程的个数

enumerate()  返回所有或者的线程的列表,不包括已经终止的线程和未开始的线程

get_ident()  返回当前线程的ID,非0整数

threading 实例的属性和方法

name   名字,标识,可以重名,getaName()、setName()获取设置这个名词

ident  线程ID,非零整数,线程启动才有ID,未启动未None。线程退出后,此ID依旧可以访问,此ID可以重复使用。注意ID只有线程退出后才能在利用,线程运行时ID唯一。

is_alive  返回线程是否活着

同一个线程只能被调用一次,再使用就重新创建

t.start() 启动线程,只能用一次,创建线程对象,start会调用run

t.run() 运行线程函数,在当前线程执行,顺序执行就是在主线程中调用了一个普通的函数而已

run不会开启新的线程,start会启动一个新线程。

启用线程使用start方法,才能启动多个线程。

串行化,并行化,对于硬件设备,并口速度快于串口,但是成本增高,并且处理难度上升。所以一般都使用串口。系统的进程都会开启多线程,但是开启越多,需要的管理成本,维护成本越高。所以线程开启合适的数目就好,并不是多多益善。

串口形容一下就是 一条车道,而并口就是有8个车道同一时刻能传送8位(一个字节)数据。 但是并不是并口快,由于8位通道之间的互相干扰。传输时速度就受到了限制。而且当传输出错时,要同时重新传8个位的数据。串口没有干扰,传输出错后重发一位就可以了。所以要比并口快。

多线程

主线程,一般来协调管理,工作线程工作,一般是这样

当使用start方法启动线程后,进程内有多个活动的线程并行的工作,就是多线程。

一个进程至少有一个线程,并作为程序的入口,这个线程就是主线程。进程至少有一个主线程

函数在不同的线程中压栈,每次调用都是不同的对象。

线程安全

在IPython中演示可以看到效果,python命令行,pycharm都不能

import threading

def worker():
    for x in range(100):
        print("{} is running.".format(threading.current_thread().name))

for x in range(1,5):
    name = "worker{}".format(x)
    t = threading.Thread(name=name,target=worker)
    t.start()

产生以上问题的原因,就是在print函数执行时,函数是分两段执行的,先打印内容,再打印换行,在这之间发生了线程的切换

这说明print函数是线程不安全的

线程执行一段代码,不会产生不确定结果,那么这段代码就是线程安全的

解决可以使print打印字符串这个不可变的整体,而将print换行,或者使用logging模块,日志处理模块,生产环境代码都使用logging

import threading
import logging,time

def worker():
    for x in range(10):
        time.sleep(0.3)
        #print("{} is running.
".format(threading.current_thread().name), end='')
        logging.info("{} is running.".format(threading.current_thread().name))

for x in range(1,5):
    name = "worker{}".format(x)
    t = threading.Thread(name=name,target=worker)
    t.start()

logging info 级别低于经警告

daemon线程和non-daemon线程

deamon不是Linux中的守护进程,有翻译之为后台进程,但感觉不够准确。

主线程退出时,看工作线程的daemon,daemon为False就等待。

只在主线程结束时才看daemon,有non-daemon就等待non-daemon所在的线程结束后结束主线程。

主线程是第一个启动的线程

父线程:如果线程A中启动了线程B,A就是B的父线程

子线程:B就是A的子线程

python中构建线程时,可以设置daemon的属性,这个属性必须在start方法前设置好。

没给定deamon,用父线程的deamon,python中在构建Thread时,会根据是否提供daemon,来判断是否是daemon线程

#源码中__init__方法中
if
daemon is not None: self._daemonic = daemon #用户传入的bool值 else: self._daemonic = current_thread().daemon
import threading,time

def foo(n):
    for i in range(n):
        print(i)
        time.sleep(1)


t1 = threading.Thread(target=foo,args=(10,),daemon=True)
t1.start()

t2 = threading.Thread(target=foo,args=(20,),daemon=False)
t2.start()

daemon属性    表示线程是否时daemon属性,这个值必须在start之前设置,否则引发RuntimeError异常

isDaemon()   是否是daemon线程

setDaemon  设置为daemon线程,必须在start方法之前设置

要点:

  线程都有daemon属性,不设置默认为None,设置为True 或者False

  不设置daemon,就取父线程的daemon来设置

  主线程是non-daemon线程,即daemon = False

  从主线程创建的所有线程,不设置dameon就都会取主线程的daemon,也就是non-daemon。

  python中主线程只有在没有活着的non-daemon时才会退出,否则只有等待。

daemon的应用场景

daemon 的出现可以简化程序员工作,让他们不用去记录和管理那些后台线程。

这个daemon概念,就是可以把线程设置为随主线程结束而结束的线程。

主要应用场景有:

  1、后台任务,如发送心跳包、监控,这种场景最多

  2、主线程工作才有用到线程,应当随主线程的结束而结束,比如主线程中的公共资源,如果主线程退出,那么工作线程使用的资源也就没有用了,一起随主线程结束。

  3、随时可以被终止的程序,如果主线程退出,想要其他工作线程一起退出。比如开启线程定时判断WEB情况的线程,没有必要一直记得,只要设置成daemon,就可以在关闭主线程是结束它

 这样可以简化程序员手动关闭线程。

 

join 方法

import time
import threading


def foo(n):
    for i in range(n):
        print(i)
        time.sleep(1)

t1 = threading.Thread(target=foo,args=(10,),daemon=True)
t1.start()

t1.join()

print("Main Thread Exiting")

上例中,主线程调用t1的join后,主线程被阻塞了,等daemon线程执行完了,主线程才退出。

join(timeout=None)是线程的标准方法,

一个线程可以被join多次

一个线程调用另一个线程的join,就是调用者被阻塞,直到被调用的线程结束。

 timeout可以指定调用者等待的时间,没有设置就是None,一直等到线程结束。

调用谁的join方法,就是要在调用的线程里等谁。

如果在主线程中,join 方法可以使daemon失效,即使是daemon线程,也要等到运行完才结束。

threading.local类

import time
import threading

class A:
    def __init__(self):
        self.x = 0
global_data = A()

def work():
    #x = 0     ## x不是全局的,能实现分别计算
    #global x  ## 全局变量下,会在一个变量上一直计算
    global_data.x = 0      ##等同于上一个,该实例的属性x只有一个
    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=work).start()

print("Main Thread Exiting")

使用多线程,每个线程完成不同的计算任务,x是局部变量时,互不干扰

如果将x设置为全局变量就会相互干扰

python提供了threading.local类,将这个实例化得到一个全局对象,对于不同的线程使用该对象存储的数据对其他线程不可见。

import time
import threading

global_data = threading.local()

def work():
    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=work).start()

print("Main Thread Exiting")

执行结果和使用局部变量的效果是一样的。

log = []
mydata = threading.local()
mydata.x = 123
def f():
    items = sorted(mydata.__dict__.items())
    log.append(items)
    mydata.number = 11
    log.append(mydata.number)
    print(mydata.number)
    print(mydata.x)  ##会报错,线程不共享该local对象在其他线程的属性,该属性与该线程绑定

import threading
thread = threading.Thread(target=f)
thread.start()
thread.join()
print(log)
print(mydata.number)   ##会报错,线程不共享其他线程内对象的属性

定时器 Timer延迟执行

类,延迟执行,继承自Thread,用来定义延迟多久之后执行一个函数

Timer提供了cancel方法,重写了run函数,就是Thread的子类,就是线程类,

 cancal方法本质上是Event类实现,只有线程执行的目标函数未执行前,才能cancal

原文地址:https://www.cnblogs.com/rprp789/p/9763676.html