周回顾并发编程与数据库08.14:UDP协议、操作系统发展史、相关名词、进程、线程、验证python多线程是否有用、锁、信号量、event事件、池、协程、数据库、MySQL、SQL与NoSQL


目录

UDP协议

操作系统发展史

相关名词

进程

线程

信号量

event事件

协程

数据库

MySQL

SQL与NoSQL


内容

UDP协议

Internet 协议集支持一个无连接的传输协议,该协议称为用户数据报协议,UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法

  1. 服务端
import socket

server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8080))
msg, address = server.recvfrom(1024)
print('msg>>>:%s' % msg.decode('utf8'))
print('address>>>:',address)
server.sendto('我是服务端 你好啊'.encode('utf8'), address)
  1. 客户端
import socket

client = socket.socket(type=socket.SOCK_DGRAM)
server_address = ('127.0.0.1', 8080)
client.sendto('我是客户端 想我了没'.encode('utf8'), server_address)
msg, address = client.recvfrom(1024)
print('msg>>>:%s' % msg.decode('utf8'))
print('address>>>:',address)
  1. 补充
    3.1 服务端不需要考虑客户端是否异常退出
    3.2 UDP不存在黏包问题(UDP多用于短消息的交互)

操作系统发展史

手工操作

程序员将对应用程序和数据的已穿孔的纸带(或卡片)装入输入机,然后启动输入机把程序和数据输入计算机内存,接着通过控制台开关启动程序针对数据运行;计算完毕,打印机输出计算结果;用户取走结果并卸下纸带(或卡片)后,才让下一个用户上机

批处理系统

联机批处理系统

主机与输入机之间增加一个存储设备——磁带,在运行于主机上的监督程序的自动控制下,计算机可自动完成:成批地把输入机上的用户作业读入磁带,依次把磁带上的用户作业读入主机内存并执行并把计算结果向输出机输出。完成了上一批作业后,监督程序又从输入机上输入另一批作业,保存在磁带上,并按上述步骤重复处理
监督程序不停地处理各个作业,从而实现了作业到作业的自动转接,减少了作业建立时间和手工操作时间,有效克服了人机矛盾,提高了计算机的利用率

脱机批处理系统

这种方式的显著特征是:增加一台不与主机直接相连而专门用于与输入/输出设备打交道的卫星机。其功能一是从输入机上读取用户作业并放到输入磁带上。二是从输出磁带上读取执行结果并传给输出机
这样,主机不是直接与慢速的输入/输出设备打交道,而是与速度相对较快的磁带机发生关系,有效缓解了主机与设备的矛盾。主机与卫星机可并行工作,二者分工明确,可以充分发挥主机的高速计算能力

多道程序系统

所谓多道程序设计技术,就是指允许多个程序同时进入内存并运行。即同时把多个程序放入内存,并允许它们交替在CPU中运行,它们共享系统中的各种硬、软件资源。当一道程序因I/O请求而暂停运行时,CPU便立即转去运行另一道程序

  1. 单道程序的运行过程:
    在A程序计算时,I/O空闲, A程序I/O操作时,CPU空闲(B程序也是同样);必须A工作完成后,B才能进入内存中开始工作,两者是串行的,全部完成共需时间=T1+T2
    image
  2. 多道程序的运行过程:
    将A、B两道程序同时存放在内存中,它们在系统的控制下,可相互穿插、交替地在CPU上运行:当A程序因请求I/O操作而放弃CPU时,B程序就可占用CPU运行,这样 CPU不再空闲,而正进行A I/O操作的I/O设备也不空闲,显然,CPU和I/O设备都处于“忙”状态,大大提高了资源的利用率,从而也提高了系统的效率,A、B全部完成所需时间<<T1+T2
    image

多道程序设计技术不仅使CPU得到充分利用,同时改善I/O设备和内存的利用率,从而提高了整个系统的资源利用率和系统吞吐量(单位时间内处理作业(程序)的个数),最终提高了整个系统的效率

分时系统

由于CPU速度不断提高和采用分时技术,一台计算机可同时连接多个用户终端,而每个用户可在自己的终端上联机使用计算机,好象自己独占机器一样
分时技术:把处理机的运行时间分成很短的时间片,按时间片轮流把处理机分配给各联机作业使用,若某个作业在分配给它的时间片内不能完成其计算,则该作业暂时中断,把处理机让给另一作业使用,等待下一轮时再继续其运行。由于计算机速度很快,作业运行轮转得很快,给每个用户的印象是,好象他独占了一台计算机。而每个用户可以通过自己的终端向系统发出各种操作控制命令,在充分的人机交互情况下,完成作业的运行


相关名词

同步和异步

用于描述任务的提交状态,关注的是消息通信机制

  1. 同步
    在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了
  2. 异步
    异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用

阻塞与非阻塞

用于描述进程的执行状态,关注的是程序在等待调用结果(消息,返回值)时的状态

  1. 阻塞
    调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回
  2. 非阻塞
    在不能立刻得到结果之前,该调用不会阻塞当前线程

同步异步与阻塞非阻塞

同步阻塞:在银行排队 并且在队伍中什么事情都不做
同步非阻塞:在银行排队 并且在队伍中做点其他事
异步阻塞:取号 在旁边座位上等着叫号 期间不做事
异步非阻塞:取号 在旁边座位上等着叫号 期间为所欲为


进程

概念

进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体

进程的调度算法

  1. 先来先服务算法
    针对耗时比较短的程序不友好

  2. 短作业优先调度
    针对耗时比较长的程序不友好

  3. 时间片轮转法+多级反馈队列
    将固定的时间均分成很多份,所有的程序来了都公平的分一份,分配多次之后如果还有程序需要运行,则将其分到下一层,越往下表示程序总耗时越长 每次分的时间片越多 但是优先级越低

进程的并行与并发

  1. 并行
    多个进程同时执行
    单个CPU肯定无法实现并行,必须要有多个CPU
  2. 并发
    多个进程看上去像同时执行就可以称之为并发
    单个CPU完全可以实现并发的效果,如果是并行那么肯定也属于并发

进程的三状态

进程执行时的间断性,决定了进程可能具有多种状态。事实上,运行中的进程可能具有以下三种基本状态

  1. 就绪状态
    进程已获得除处理器外的所需资源,等待分配处理器资源;只要分配了处理器进程就可执行。就绪进程可以按多个优先级来划分队列。例如,当一个进程由于时间片用完而进入就绪状态时,排入低优先级队列;当进程由I/O操作完成而进入就绪状态时,排入高优先级队列

  2. 运行状态
    进程占用处理器资源;处于此状态的进程的数目小于等于处理器的数目。在没有其他进程可以执行时(如所有进程都在阻塞状态),通常会自动执行系统的空闲进程

  3. 阻塞状态
    由于进程等待某种条件(如I/O操作或进程同步),在条件满足之前无法继续执行。该事件发生前即使把处理器资源分配给该进程,也无法运行

所有的进程要想被运行,必须先经过就绪态;运行过程中如果出现了IO操作,则进入阻塞态;运行过程中如果时间片用完,则继续进入就绪态;阻塞态要想进入运行态必须先经过就绪态
image

代码创建进程

  1. 启动脚本
from multiprocessing import Process
import time

def task(name):
    print(f'{name}正在运行')
    time.sleep(3)
    print(f'{name}运行结束')

if __name__ == '__main__':
    p = Process(target=task, args=('jason',))  # 创建一个进程对象
    p.start()  # 告诉操作系统创建一个进程(异步操作)
    print('主进程')

image

  1. 面向对象
from multiprocessing import Process
import time

def task(name):
    print(f'{name}正在运行')
    time.sleep(3)
    print(f'{name}运行结束')

class MyProcess(Process):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self):
        print(f'{self.name}正在运行')
        time.sleep(5)
        print(f'{self.name}运行结束')

if __name__ == '__main__':
    obj = MyProcess('jason')
    obj.start()
    print('主进程')

image

join方式

join:主进程等待子进程运行结束之后再运行
推导步骤1:直接在主进程代码中添加time.sleep()
不合理,因为无法准确把握子进程执行的时间
推导步骤2:join方法

from multiprocessing import Process
import time

def task(name, n):
    print(f'{name}正在运行')
    time.sleep(n)
    print(f'{name}运行结束')

if __name__ == '__main__':
    p1 = Process(target=task, args=('jason', 1))  
    p2 = Process(target=task, args=('kevin', 2))  
    p3 = Process(target=task, args=('jerry', 3))
    start_time = time.time()
    p1.start()
    p2.start()
    p3.start()
    p1.join()
    p2.join()
    p3.join()
    end_time = time.time() - start_time
    print('总耗时:%s' % end_time)
    print('主进程')

image

进程间数据默认隔离

多个进程数据彼此之间默认是相互隔离的
如果真的想交互,则需要借助于’管道’或者’队列’

from multiprocessing import Process

money = 100

def task():
    global money
    money = 666
    print('子进程打印的money', money)

if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    p.join()
    print('父进程打印的money', money)

image

生产者消费者模型

又称有限缓冲问题,是一个多进程同步问题的经典案例。该问题描述了共享固定大小缓冲区的两个进程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据
要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题,常用的方法有信号灯法等。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形

进程相关方法

1.查看进程号

from multiprocessing import current_process
import os

current_process().pid
os.getpid()
os.getppid()

2.销毁子进程
p1.terminate()
3.判断进程是否存活
p1.is_alive()

守护进程

守护进程(daemon)是一类在后台运行的特殊进程,用于执行特定的系统任务。很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭。另一些只在需要的时候才启动,完成任务后就自动结束

import time

def task(name):
    print('8023 %s 3208' % name)
    time.sleep(3)
    print('8023 %s 3208' % name)

if __name__ == '__main__':
    p = Process(target=task, args=('Aurora',))
    p.daemon = True  # 将子进程设置为守护进程:主进程代码结束 子进程立刻结束
    p.start()
    print('9264')

image

from multiprocessing import Process
import time

def task(name):
    print('8023 %s 3208' % name)
    time.sleep(3)
    print('8023 %s 3208' % name)

if __name__ == '__main__':
    p = Process(target=task, args=('Aurora',))
    p.start()
    p.daemon = True  # 必须在start之前执行
    print('9264')

image

僵尸进程与孤儿进程

  1. 僵尸进程
    当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。如果父进程先退出 ,子进程被init接管,子进程退出后init会回收其占用的相关资源

  2. 孤儿进程
    在操作系统领域中,孤儿进程指的是在其父进程执行完成或被终止后仍继续运行的一类进程。这些孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作


线程

线程理论

是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务

  1. 一个进程至少含有一个线程
  2. 多进程与多线程的区别
维度 多进程 多线程
数据的共享与同步 数据是分开的,共享复杂,需要用IPC;同步简单 多线程共享进程数据,共享简单;同步复杂
内存 内存占用较多 内存占用较小
创建、销毁、切换 创建销毁、切换复杂,速度慢 创建销毁、切换简单,速度快
可靠性 进程间不会相互影响 一个线程挂掉将导致整个进程挂掉
  1. 同一个进程下多个线程之间资源共享

创建线程的两种方式

  1. 使用Thread模块
from threading import Thread
import time

def task(name):
    print(f'{name}正在运行')
    time.sleep(3)
    print(f'{name}运行结束')

if __name__ == '__main__':
    t = Thread(target=task, args=('jason',))
    t.start()
    print('主线程')

image

  1. 使用类继承Thread
from threading import Thread
import time

class MyThread(Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self):
        print(f'{self.name}正在运行')
        time.sleep(3)
        print(f'{self.name}运行结束')

obj = MyThread('jason')
obj.start()
print('主线程')

image

线程join方法

from threading import Thread
import time

def task():
    print('正在执行')
    time.sleep(3)
    print('运行结束')

t = Thread(target=task)
t.start()
t.join()
print('主线程')

同一个进程下线程间数据共享

from threading import Thread

money = 1000

def func():
    global money
    money = 666

t = Thread(target=func)
t.start()
t.join()
print(money)

image

线程对象相关方法

  1. 进程号
    同一个进程下开设的多个线程拥有相同的进程号
  2. 线程名
from threading import Thread, current_thread
	current_thread().name	# 拿取线程名
 	主:MainThread	子:Thread-N
  1. 进程下的线程数
    active_count()

守护线程

守护线程伴随着被守护的线程的结束而结束,要注意的是进程下所有的非守护线程结束,主线程(主进程)才能真正结束

from threading import Thread
import time

def task():
    print('子线程运行task函数')
    time.sleep(3)
    print('子线程运行task结束')

t = Thread(target=task)
# t.daemon = True
t.start()
# t.daemon = True
print('主线程')

验证python多线程是否有用

想要验证python多线程是否有用需要分情况,cpu的个数以及是IO密集型还是计算密集型

  1. 单个CPU
多进程 多线程
IO密集型 申请额外的空间 消耗更多的资源 消耗资源相对较少 通过多道技术
计算密集型 请额外的空间 消耗更多的资源(总耗时+申请空间+拷贝代码+切换) 消耗资源相对较少 通过多道技术(总耗时+切换)
  1. 多个CPU
多进程 多线程
IO密集型 总耗时(单个进程的耗时+IO+申请空间+拷贝代码) 总耗时(单个进程的耗时+IO)
计算密集型 总耗时(单个进程的耗时) 总耗时(多个进程的综合)
  1. 计算密集型

多进程:5.665567398071289

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


def work():
    # 计算密集型
    res = 1
    for i in range(1, 100000):
        res *= i


if __name__ == '__main__':
    start_time = time.time()
    p_list = []
    for i in range(12):  # 一次性创建12个进程
        p = Process(target=work)
        p.start()
        p_list.append(p)
    for p in p_list:  # 确保所有的进程全部运行完毕
        p.join()
	print('总耗时:%s' % (time.time() - start_time)) 

多线程:30.233906745910645

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


def work():
    # 计算密集型
    res = 1
    for i in range(1, 100000):
        res *= i


if __name__ == '__main__':
    start_time = time.time()
    t_list = []
    for i in range(12):
        t = Thread(target=work)
        t.start()
        t_list.append(t)
    for t in t_list:
        t.join()
	print('总耗时:%s' % (time.time() - start_time))
  1. IO密集型

多线程:0.0149583816528320

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


def work():
    time.sleep(2)   # 模拟纯IO操作


if __name__ == '__main__':
    start_time = time.time()
    t_list = []
    for i in range(100):
        t = Thread(target=work)
        t.start()
    for t in t_list:
        t.join()
	print('总耗时:%s' % (time.time() - start_time))

多进程:0.6402878761291504

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


def work():
    time.sleep(2)   # 模拟纯IO操作


if __name__ == '__main__':
    start_time = time.time()
    p_list = []
    for i in range(100):
        p = Process(target=work)
        p.start()
    for p in p_list:
        p.join()
	print('总耗时:%s' % (time.time() - start_time))

互斥锁

在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为” 互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象

模拟抢票:
将并发变成串行,虽然牺牲了程序的执行效率但是保证了数据安全

  1. 使用
from multiprocessing import Process, Lock
import time
import json
import random
 
# 查票
def search(name):
    with open(r'data.json', 'r', encoding='utf8') as f:
        data = json.load(f)
    print('%s在查票 当前余票为:%s' % (name, data.get('ticket_num')))
 
# 买票
def buy(name):
    # 再次确认票
    with open(r'data.json', 'r', encoding='utf8') as f:
        data = json.load(f)
    # 模拟网络延迟
    time.sleep(random.randint(1, 3))
    # 判断是否有票 有就买
    if data.get('ticket_num') > 0:
        data['ticket_num'] -= 1
        with open(r'data.json', 'w', encoding='utf8') as f:
            json.dump(data, f)
        print('%s买票成功' % name)
    else:
        print('%s很倒霉 没有抢到票' % name)
 
def run(name):
    search(name)
	mutex.acquire()  # 抢锁
    buy(name)
	mutex.release()  # 释放锁
 
if __name__ == '__main__':
	mutex = Lock()	# 产生锁
    for i in range(10):
        p = Process(target=run, args=('用户%s'%i, ))
        p.start()
  1. 注意:
    互斥锁只应该出现在多个程序操作数据的地方,其他位置尽量不要加

  2. 行锁
    访问数据库的时候,锁定整个行数据,防止并发错误
    开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高

  3. 表锁
    访问数据库的时候,锁定整个表数据,防止并发错误
    开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突概率高,并发度最低

  4. 悲观锁
    每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁

  5. 乐观锁
    每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制


GIL全局解释器锁

在CPython中,全局解释器锁(GIL)是一个互斥锁,它可以防止多个本机线程同时执行Python字节码。这个锁是必要的,主要是因为CPython的内存管理不是线程安全的。(然而,自从GIL存在以来,其他特性已经成长为依赖于它所实施的保证。)

  1. GIL的研究是Cpython解释器的特点 不是python语言的特点
  2. GIL本质也是一把互斥锁
  3. GIL的存在使得同一个进程下的多个线程无法同时执行(关键)
    言外之意:单进程下的多线程无法利用多核优势 效率低
  4. GIL的存在主要是因为cpython解释器中垃圾回收机制不是线程安全的
  5. 误解1:python的多线程就是垃圾 利用不到多核优势
    python的多线程确实无法使用多核优势 但是在IO密集型的任务下是有用的
  6. 误解2:既然有GIL 那么以后我们写代码都不需要加互斥锁
    不对 GIL只确保解释器层面数据不会错乱(垃圾回收机制)
    针对程序中自己的数据应该自己加锁处理
  7. 所有的解释型编程语言都没办法做到同一个进程下多个线程同时执行

GIL验证

  1. GIL的存在
from threading import Thread

money = 100


def task():
    global money
    money -= 1


t_list = []
for i in range(100):
    t = Thread(target=task)
    t.start()
    t_list.append(t)  # [线程1 线程2 线程3 ... 线程100]
for t in t_list:
    t.join()
# 等待所有的线程运行结束 查看money是多少
print(money)
  1. GIL的特点
from threading import Thread
import time

money = 100


def task():
    global money
    tmp = money
    time.sleep(0.1)
    money = tmp - 1


t_list = []
for i in range(100):
    t = Thread(target=task)
    t.start()
    t_list.append(t)  # [线程1 线程2 线程3 ... 线程100]
for t in t_list:
    t.join()
# 等待所有的线程运行结束 查看money是多少
print(money)  # 99
"""GIL不会影响程序层面的数据也不会保证它的修改是安全的要想保证得自己加锁"""
from threading import Thread,Lock
import time

money = 100
mutex = Lock()


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


t_list = []
for i in range(100):
    t = Thread(target=task)
    t.start()
    t_list.append(t)  # [线程1 线程2 线程3 ... 线程100]
for t in t_list:
    t.join()
# 等待所有的线程运行结束 查看money是多少
print(money)

死锁现象

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

from threading import Thread, Lock
import time

mutexA = Lock()  # 类名加括号每执行一次就会产生一个新的对象
mutexB = Lock()  # 类名加括号每执行一次就会产生一个新的对象


class MyThread(Thread):
    def run(self):
        self.func1()
        self.func2()

    def func1(self):
        mutexA.acquire()
        print(f'{self.name}抢到了A锁')
        mutexB.acquire()
        print(f'{self.name}抢到了B锁')
        mutexB.release()
        print(f'{self.name}释放了B锁')
        mutexA.release()
        print(f'{self.name}释放了A锁')

    def func2(self):
        mutexB.acquire()
        print(f'{self.name}抢到了B锁')
        time.sleep(1)
        mutexA.acquire()
        print(f'{self.name}抢到了A锁')
        mutexA.release()
        print(f'{self.name}释放了A锁')
        mutexB.release()
        print(f'{self.name}释放了B锁')


for i in range(10):
    t = MyThread()
    t.start()

信号量

信号量在不同的知识体系中,意思可能有区别,在并发编程中,信号量就是多把互斥锁
我们之前使用Lock产生的是单把锁,类似于单间厕所,信号量相当于一次性创建多间厕所,类似于公共厕所

from threading import Thread, Lock, Semaphore
import time
import random


sp = Semaphore(5)  # 一次性产生五把锁


class MyThread(Thread):
    def run(self):
        sp.acquire()
        print(self.name)
        time.sleep(random.randint(1, 3))
        sp.release()


for i in range(20):
    t = MyThread()
    t.start()

event事件

子进程/子线程之间可以彼此等待彼此
需求:子A运行到某一个代码位置后发信号告诉子B开始运行

from threading import Thread, Event
import time

event = Event()  # 类似于造了一个红绿灯


def light():
    print('红灯亮着的 所有人都不能动')
    time.sleep(3)
    print('绿灯亮了 油门踩到底 给我冲!!!')
    event.set()


def car(name):
    print('%s正在等红灯' % name)
    event.wait()
    print('%s加油门 飙车了' % name)


t = Thread(target=light)
t.start()
for i in range(20):
    t = Thread(target=car, args=('熊猫PRO%s' % i,))
    t.start()

进程池和线程池

在实际应用中是不可以无限制的开进程和线程,因为会造成内存溢出,受限于硬件水平,我们在开设多进程或者多线程的时候,还需要考虑硬件的承受范围

降低程序的执行效率,保证计算机硬件的安全,是一个量的单位
进程池 提前创建好固定个数的进程供程序使用 后续不会再创建
线程池 提前创建好固定个数的线程供程序使用 后续不会再创建
  1. 线程池
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
from threading import current_thread
import os
import time

pool = ThreadPoolExecutor(5)  # 固定产生五个线程


def task(n):
    print(os.getpid())
    time.sleep(1)
    return '返回的结果'


def func(*args, **kwargs):
    print('func', args, kwargs)
    print(args[0].result())


if __name__ == '__main__':
    for i in range(20):
        pool.submit(task, 123).add_done_callback(func)
  1. 进程池
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
from threading import current_thread
import os
import time

pool = ProcessPoolExecutor(5)  # 固定产生五个线程


def task(n):
    print(os.getpid())
    time.sleep(1)
    return '返回的结果'


def func(*args, **kwargs):
    print('func', args, kwargs)
    print(args[0].result())


if __name__ == '__main__':
    for i in range(20):
        pool.submit(task, 123).add_done_callback(func)

协程

进程:资源单位
线程:执行单位
协程:单线程下实现并发(效率极高)
在代码层面欺骗CPU 让CPU觉得我们的代码里面没有IO操作
实际上IO操作被我们自己写的代码检测 一旦有 立刻让代码执行别的

import time
from gevent import monkey;

monkey.patch_all()  # 固定编写 用于检测所有的IO操作(猴子补丁)
from gevent import spawn


def func1():
    print('func1 running')
    time.sleep(3)
    print('func1 over')


def func2():
    print('func2 running')
    time.sleep(5)
    print('func2 over')


if __name__ == '__main__':
    start_time = time.time()
    # func1()
    # func2()
    s1 = spawn(func1)  # 检测代码 一旦有IO自动切换(执行没有io的操作 变向的等待io结束)
    s2 = spawn(func2)
    s1.join()
    s2.join()
    print(time.time() - start_time)  # 8.01237154006958   协程 5.015487432479858

协程实现TCP服务端并发

import socket
from gevent import monkey;monkey.patch_all()  # 固定编写 用于检测所有的IO操作(猴子补丁)
from gevent import spawn


def communication(sock):
    while True:
        data = sock.recv(1024)
        print(data.decode('utf8'))
        sock.send(data.upper())


def get_server():
    server = socket.socket()
    server.bind(('127.0.0.1', 8080))
    server.listen(5)
    while True:
        sock, addr = server.accept()  # IO操作
        spawn(communication, sock)

s1 = spawn(get_server)
s1.join()

总结

如何不断的提升程序的运行效率
多进程下开多线程,多线程下开协程


数据库

数据存储演变史

  1. 文本文件
    文件路径不一致: C:/a.txt D:/aaa/b.txt E:/ccc.txt
    数据格式不一致: jason|123 tony$123 [email protected]
    程序彼此无法兼容,没有统一的标准

  2. 软件开发目录规范
    规定了数据文件的大致存储位置: db文件夹
    针对数据格式还是没有完全统一: 比如统一json文件但是内部键值对不同
    文件查找变得统一,但是没有解决格式问题

  3. 数据库服务
    统一了存取位置,也统一了数据格式

数据库软件应用史

  1. 单机
    不同计算机上的相同程序,数据无法共享,数据库服务全部在本地完成

  2. 网络
    数据统一基于网络保存到某个固定的服务器上,实现不同计算机上的相同程序,数据可以共享,数据库服务单独在网络架设

本质

  1. 底层原理角度
    数据库指的是专用用于操作数据的进程,运行在内存中的代码

  2. 现实应用角度
    数据库指的是拥有操作界面的应用程序

类型

关系型数据库

  1. 数据库组织方式有明确的表结构
    id name password
    关系型数据库存取数据的方式可以看成是表格
  2. 表与表之间可以建立数据库层面的关系
    只要获取到用户表的一条数据 就可以获取到与之相关的其他表数据
  3. 具体数据库
    MySQL:开源、使用最为广泛、数据库学习必学
    PostgreSQL:开源、支持二次开发
    MariaDB:开源、与MySQL是同一个作者、用法也极其相似
    Oracle:收费、安全性极高、主要用于银行及各大重要机关
    sqlite:小型数据库、主要用于本地测试(django框架自带该数据库)

非关系型数据库

  1. 数据的组织方式没有明确的表结构,是以K:V键值对的形式组织的
    {‘username’:’kevin’,’pwd’:123}
  2. 数据之间无法直接建立数据库层面的关系
  3. 类型
    redis:目前最火 使用频率最高的缓存型数据库
    mongoDB:稳定型数据库、最像关系型的非关系型、主要用于爬虫和大数据
    memcache:已经被redis淘汰

重要概念

  1. 库:按照数据结构来组织、存储、管理数据的建立在计算机中存储设备中的仓库,可理解为文件夹

  2. 表:包含数据库中所有数据的数据库对象,是数据库中用来存储数据的对象,是有结构的数据集合,可理解为文件夹中的文件

  3. 记录:对应数据源中一行信息的一组完整的相关信息,表的“行”为记录,“列”为字段,可理解为文件中的一行行数据

  4. 相关命令:
    查看所有数据库:show databases;
    查看所有的表:show tables;
    查看user表里所有的记录:select * from mysql.user;


MySQL

简介

  1. 版本
    操作几乎没有区别,主要底层运作不一样
    5.6.X:前几年使用频率最高的版本
    5.8.X:最近尝试迁移的版本,频率逐年增加
    8.0.X:最新版本,功能强大,线上环境几乎不用,但本地自己非常好用
  2. 下载与安装
    访问官网→点击DOWNLOADS→点击GPL→点击community server→点击archives→点击download→下载的压缩包里含有服务端和客户端,支持本地操作
  3. 主要文件介绍
    bin文件夹:mysqld.exe服务端、mysql.exe客户端
    data文件夹:存储数据
    my——default.ini:默认配置文件

基本使用

  1. 启动服务端
    查找到mysqld.exe文件的位置后直接输入mysqld启动服务端
    启动服务端时可能会报错,报错可百度解决,启动服务端后不要关闭cmd窗口,此时的打开mysqld的cmd窗口就相当于服务端,关闭后为服务端也会一起关闭
    image

  2. 启动客户端
    cmd窗口在启动mysql服务端后会一直运行服务端,想要输入其他cmd命令就需要重新开启一个cmd窗口
    启动客户端后直接输入mysql命令后会进入游客模式,该模式下功能会少很多
    image
    想使用完整版就要使用用户名和密码登录,mysql管理员默认账号用户名是root密码为空
    命令语句:mysql -u用户名 -p密码
    image

  3. 退出
    命令语句:exitquit
    image
    image

系统服务制作

  1. 添加环境变量解决需要切换路径查找文件的缺陷
  2. 将MySQL服务端制作成系统服务,就没有开启服务端后才能使用的限制
    以管理员身份打开cmd命令窗口,输入命令mysql --installl,将MySQL添加到系统服务中后,输入命令net start mysql,启动mysql服务
    查看系统服务命令services.msc
    关闭mysql服务端net stop mysql
    移除系统服务:确定关闭服务后,输入移除命令mysql --remove

密码相关操作

  1. 修改密码
    想要mysql管理员修改密码时,需要退出mysql客户端使用命令mysqladmin,该命令是一个管理员脚本工具,可以执行一些常规的命令包括修改密码,因默认管理员密码为空,所以第一次修改密码 -p 后也为空
    修改密码命令mysqladmin -u用户名 -p原密码 -password 新密码
    image

  2. 忘记密码
    直接重装/拷贝对应文件
    先关闭服务端 然后以不需要校验用户身份的方式启动 再修改 最后再安装正常方式启动
    1.net stop mysql
    2.mysqld –skip-grant-tables
    3.mysql -uroot -p
    4.update mysql.user set password=password(123) where Host=’localhost’ and User=’root’;
    5.net stop mysql
    6.net start mysql


SQL和NoSQL

数据库的服务端支持各种语言充当客户端,为了能够兼容所有类型的客户端,目前使用的策略是制定统一的标准:SQL语句、NoSQL语句
SQL语句:操作关系型数据库的语法,SQL语句的结束符是分号;,取消SQL语句的执行/c
NoSQL语句:非关系型数据库的语法

针对库的基本SQL语句

  1. 增加数据库
    create database 库名;
    image

  2. 查看数据库
    查看当前所有数据库名: show databases;
    image
    查看指定数据库详细信息: show create database 库名;
    image
    查看当前所在库名: select database();,如果没有切换指定库,默认为NULL
    image

  3. 切换当前操作的默认库
    use 库名;由于use是特殊关键字,结束不加分号也可以执行
    image

  4. 修改数据库编码
    alter database 库名 charset='编码表';
    image

  5. 删除数据库
    drop database 库名;
    image

针对表的基本SQL语句

  1. 增加表
    create table 表名(字段名 字段类型,字段名 字段类型);
    image

  2. 查看表
    查看当前库中所有的表: show tables;
    image
    查看当前库中指定表的结构信息: show create table 表名;
    image
    查看表的具体字段的信息: describe 表名; 可以简写成 desc 表名;
    image

  3. 修改表名
    alter table 旧表名 rename 新表名
    image

  4. 删除表
    drop table 表名;
    image

针对记录的基本SQL语句

  1. 增加记录
    insert into 表名 values(数据,数据),(数据,数据); 括号中的数据按照创建表时的字段所一一对应,想添加多少条记录就输入多少个括号
    image

  2. 查看记录
    查看表中所有的记录: select * from 表名; * 号表示查看所有的记录
    image
    查看指定字段的记录,可同时查看多个字段: select 字段1,字段2 from 表名;
    image
    在当前库中查看其他库中的指定表: select * from 库名.表名
    image

  3. 修改数据
    update 表名 set 字段名=新数据 where 筛选条件;
    image

  4. 删除数据
    按照条件删除数据: delete from 表名 where 筛选条件;
    image
    删除表中所有的数据: delete from 表名;
    image

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/280605.html

(0)
上一篇 2022年8月15日
下一篇 2022年8月15日

相关推荐

发表回复

登录后才能评论