一、多线程
1.死锁与递归锁
死锁:指两个或两个以上进程或线程执行中,因争夺资源造成的一种互相等待的现象。

1 from threading import Thread,Lock,RLock 2 import time 3 4 mutexA = Lock() 5 mutexB = Lock() 6 7 # mutexA = mutexB = RLock() 8 class MyThread(Thread): 9 def run(self): 10 self.f1() 11 self.f2() 12 def f1(self): 13 mutexA.acquire() 14 print("%s 拿到了A锁"%self.name) 15 mutexB.acquire() 16 print("%s 拿到了B锁"%self.name) #第线程二个抢到A锁后,等待B锁 17 mutexB.release() 18 mutexA.release() #第一个线程释放A锁,立即被第二个线程抢到 19 def f2(self): 20 mutexB.acquire() #第一个线程等待B锁 21 print("%s 拿到了B锁"%self.name) 22 time.sleep(0.1) 23 mutexA.acquire() 24 print("%s 拿到了A锁"%self.name) 25 mutexA.release() 26 mutexB.release() 27 28 if __name__=="__main__": 29 for i in range(10): 30 t = MyThread() 31 t.start()
可通过使用递归锁RLock解决问题,RLock内部维护一个lock和counter变量,counter记录acquire次数,使得资源可以被多次acquire。知道一个线程内所有acquire都被release,其他线程才能获得资源。
2.信号量Semaphore
Semaphore管理内置计数器,每调用acquire,内置计数器减一,调用release则加一,计数器不可小于0,当计数器为0时,acquire将阻塞线程直到其他线程调用release。
Semaphore与进程池不同,进程/线程池Pool(5)只能产生5个进程/线程,而信号量中的数字只是限定并发执行的进程/线程。
from threading import Thread,Semaphore,current_thread import time,random sm = Semaphore(5) def task(): with sm: print("%s is running"%current_thread().getName()) time.sleep(random.randint(1,3)) if __name__=="__main__": for i in range(20): t = Thread(target=task) t.start()
3.Event
如果程序中其他线程需要通过判断某个线程的状态来确定自己下一步操作,就需要使用threading库中的Event对象。对象包含一个可以由线程设置的信号标志,它允许线程等待某些事件发生。
from threading import Event event = Event() event.isSet() #返回event的状态值,默认为False event.wait() #如果event.isSet()==False 将阻塞 event.set() #设置event状态值为True,所有阻塞池的线程进入就绪状态,等待操作系统调度 event.clear() #回复event的状态为False
from threading import Thread,Event,current_thread import time event = Event() def check(): print("checking MySQL...") time.sleep(3) event.set() #event设置为True,表示检测到服务器起来了 def conn(): count=1 while not event.is_set(): #当event设置为True后,表示连接成功 if count>3: #尝试3次连接,失败则抛出异常 raise TimeoutError("超时") print("%s try to connect MySQL time %s"%(current_thread().getName(),count)) event.wait(1) #阻塞一秒 count+=1 print("%s connected Mysql"%current_thread().getName()) if __name__=="__main__": t1 = Thread(target=check) t2 = Thread(target=conn) t3 = Thread(target=conn) t4 = Thread(target=conn) t1.start() t2.start() t3.start() t4.start()
4.线程queue
线程queue直接使用import queue即可,用法同进程Queue
import queue q = queue.Queue(3) q.put(1) q.put(2) q.put(3) #q.put(4) #队列已满,会阻塞 # q.put_nowait(4) #抛异常 # q.put(4,block=True,timeout=3) #如果队列满了,阻塞等待3秒 # print(q.get()) #先进先出 # print(q.get()) # print(q.get()) q = queue.PriorityQueue(3) #优先级队列 q.put((10,"a")) #元组中,第一个为优先级,数字越小,取出时优先级越高 q.put((-3,"b")) q.put((100,"c")) print(q.get()) #(-3, 'b') print(q.get()) #(10, 'a') print(q.get()) #(100, 'c')
5.进程池和线程池
concurrent.futures模块提供了高度封装的异步调用接口
ThreadPoolExecutor:线程池,提供异步调用接口
ProcessPoolExecutor:进程池,提供异步调用接口
主要方法:
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) 回调函数
#提交任务的两种方式: #同步调用:执行完任务后,就原地等待,等到任务执行完毕,拿到返回值,才能继续下一行代码,导致程序串行执行。 #异步调用+回调机制:执行完任务后,不再原地等待,任务一旦执行完毕就会触发回调函数,程序是并发执行的。 from concurrent.futures import ThreadPoolExecutor from threading import current_thread import requests,time def get(url): print("%s GET %s"%(current_thread().getName(),url)) response = requests.get(url) time.sleep(2) if response.status_code==200: return {"url":url,"content":response.text} def parse(res): res = res.result() print("parse:[%s] res:[%s]"%(res["url"],len(res["content"]))) if __name__=="__main__": pool = ThreadPoolExecutor(2) #线程池开两个线程,用于执行任务 urls = [ "https://www.baidu.com", "http://www.sina.com.cn", "http://www.163.com", "https://www.cnblogs.com", "https://www.cnblogs.com", "https://www.cnblogs.com", "https://www.cnblogs.com", "https://www.cnblogs.com", "https://www.cnblogs.com" ] for url in urls: pool.submit(get,url).add_done_callback(parse) #回调函数将前一个函数执行结果作为输入参数 pool.shutdown(wait=True)
6.协程
在单线程下,程序中出现io操作时,如果能在程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能在一个任务遇到io阻塞时切换到另一个任务,可以保证该线程能够最大限度的处于就绪态,即随时可以被cpu执行的状态,相当于在用户程序级别将自己的io操作最大限度的隐藏了。让操作系统以为该线程一直在计算,io比较少,将会得到更多的cpu执行时间。
协程就是单线程下的并发,又称为微线程。协程是一种用户态的轻量级线程,由用户程序自己控制调度。
协程的优点:
协程开销更小,术语程序级别的切换,操作系统完全感知不到,更轻量级。
单线程内就可以实现并发效果,最大限度的利用cpu。
协程的缺点:
协程本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程开启多个线程,每个线程开启协程的情况。
协程指大那个线程,因而一旦协程出现阻塞,会阻塞整个线程。
协程的特点:
a) 必须在一个线程里实现并发。
b) 修改共享数据不需要加锁。
c) 用户程序里自己保存多个控制流的上下文栈。
d) 一个协程遇到IO操作会自动刚切换到其他协程(yield、greenlet无法实现检测IO,gevent模块可以)
使用greenlet实现多任务切换。
#pip3 install greenlet from greenlet import greenlet def eat(name): print("%s eat 1"%name) g2.switch("bbb") #第二步,切换到play函数 print("%s eat 2"%name) g2.switch() #第四步,切换到play函数 def play(name): print("%s play 1"%name) g1.switch() #第三步,切换到eat函数 print("%s play 2"%name) g1 = greenlet(eat) g2 = greenlet(play) g1.switch("aaa") #第一步:切换到eat函数
从执行结果看,感觉是可以实现两个不同功能同时执行,但是在没有io的情况下,会降低程序执行速度。
Gevent实现协程
#pip3 install gevent import gevent g1 = gevent.spawn(func1,1,2,3,x=4,y=5) #创建一个协程对象g1,spawn括号中第一个参数是函数名,如eat,后面可以有多个参数,都是传递给函数eat的。 g2 = gevent.spawn(func2) g1.join() #等待g1结束 g2.join() #等待g2结束 # gevent.joinall([g1,g2]) #上面两步合为一步 g1.value #得到func的返回值
from gevent import monkey;monkey.patch_all() #替换所有io函数 import gevent import time def eat(name): print("%s eat 1"%name) time.sleep(3) print("%s eat 2"%name) def play(name): print("%s play 1"%name) time.sleep(3) print("%s play 2"%name) start = time.time() g1 = gevent.spawn(eat,"aaa") g2 = gevent.spawn(play,"bbb") gevent.joinall([g1,g2]) stop = time.time() print("runtime is %s"%str(stop-start)) #虽然两个函数均有三秒延迟,单线程情况下至少6秒,协程可实现3秒执行完。
二、MySQL
1、相关概念
数据库服务器:运行数据库管理软件
数据库管理软件:管理数据库
数据库:即文件夹,用于组织文件/表
表:即文件,用来存放多行内容/多条记录
MySQL是关系型数据库管理系统,在WEB应用方面,MySQL是最好的RDBMS(关系数据库管理系统)之一。
2.库操作
information_schema:虚拟库,不占用磁盘资源,存储数据库启动后的参数,如用户表信息、列信息、权限信息、字符信息等。
performance_schema:MySQL5.5开始新增的数据库,用于搜集数据库服务器性能参数,记录处理查询请求时发生的各种事件、锁等现象。
mysql:授权库,存储用户权限信息。
test:MySQL数据库系统自动创建的测试数据库。
创建数据库:create database 数据库名 charset utf8; (命名规则:可由数字、字母、下划线、@、#、$组成,区分大小写,最长128位)
数据库相关操作:
a) 查看数据库:
show databases;
show create database test;
select database(); 查看当前数据库
b) 选择数据库
use 数据库名
c) 删除数据库
drop database 数据库名;
d) 修改数据库
alter database test charset utf8;
3.数据类型
整数类型:
包括TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT。
指定宽度时,仅仅只是指定查询结果的显示宽度,与存储范围无关。整数类型显示宽度使用默认即可。
浮点类型:
包括FlOAT、DOUBLE
FLOAT:为单精度浮点数(非准确小数点)。FLOAT[(M,D)] [UNSIGNED] [ZEROFILL] ,m是数字总数个数,d是小数点后个数。m最大值为255,d最大值为30
DOUBLE:双精度浮点数(非准确小数),DOUBLE[(M,D)] [UNSIGNED] [ZEROFIL] ,m是数字总个数,d是小数点后个数,m最大值为255,d最大值为30。
日期类型:
包括DATE、TIME、DATETIME、TIMESTAMP、YEAR
YEAR:YYYY (1901/2155)
DATE: YYYY-MM-DD (1000-01-01/9999-12-31)
TIME: HH:MM:SS ('-838:59:59'/'838:59:59')
DATETIME: YYYY-MM-DD HH:MM:SS (1000-01-01/9999-12-31 23:59:59) ,存储时间和时区无关,8字节存储空间,默认为null。
TIMESTAMP: YYYMMDD HHMMSS (1970-01-01 00:00:00/2037) ,存储时间和显示时间与时区有关,4字节存储空间,默认为当前时间。
字符串处理:
包括char和varchar,括号中数字均为字符长度,不同字符集所占字节数可能不同。
char:字符定长(0-255),浪费空间,存取速度快。
varcha:字符变长(0-65535),精确,节省空间,存取速度慢。
4.表操作
创建表:
create table 表名(
字段名1 类型 [(宽度)约束条件],
字段名2 类型 [(宽度)约束条件],
字段名3 类型 [(宽度)约束条件],
);

mysql> create table t1( -> id int, -> name varchar(50), -> sex enum('male','female'), -> age int(3) -> ); Query OK, 0 rows affected (0.77 sec) mysql> show tables; +---------------+ | Tables_in_db1 | +---------------+ | t1 | +---------------+ 1 row in set (0.00 sec) mysql> desc t1; +-------+-----------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+-----------------------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | | name | varchar(50) | YES | | NULL | | | sex | enum('male','female') | YES | | NULL | | | age | int(3) | YES | | NULL | | +-------+-----------------------+------+-----+---------+-------+ 4 rows in set (0.02 sec) mysql> select id,name,sex,age from t1; Empty set (0.00 sec) mysql> select * from t1; Empty set (0.00 sec)
插入数据

mysql> insert into t1 value -> (1,'xxx',10,"male"), -> (2,'aaa',11,'female') -> ; Query OK, 2 rows affected, 4 warnings (0.40 sec) Records: 2 Duplicates: 0 Warnings: 4 mysql> select * from t1; +------+------+------+------+ | id | name | sex | age | +------+------+------+------+ | 1 | xxx | | 0 | | 2 | aaa | | 0 | +------+------+------+------+ 2 rows in set (0.00 sec) mysql> insert into t1(id) values -> (3), -> (4); Query OK, 2 rows affected (0.34 sec) Records: 2 Duplicates: 0 Warnings: 0 mysql> select * from t1; +------+------+------+------+ | id | name | sex | age | +------+------+------+------+ | 1 | xxx | | 0 | | 2 | aaa | | 0 | | 3 | NULL | NULL | NULL | | 4 | NULL | NULL | NULL | +------+------+------+------+ 4 rows in set (0.00 sec)
查看表结构
mysql> describe t1; +-------+-----------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+-----------------------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | | name | varchar(50) | YES | | NULL | | | sex | enum('male','female') | YES | | NULL | | | age | int(3) | YES | | NULL | | +-------+-----------------------+------+-----+---------+-------+ 4 rows in set (0.00 sec) mysql> show create table t1\G; *************************** 1. row *************************** Table: t1 Create Table: CREATE TABLE `t1` ( `id` int(11) DEFAULT NULL, `name` varchar(50) DEFAULT NULL, `sex` enum('male','female') DEFAULT NULL, `age` int(3) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 1 row in set (0.00 sec)
修改表
1.修改表名 ALTER TABLE 表名 RENAME 新表名; 2.增加字段 ALTER TABLE 表名 ADD 字段名 数据类型 [完整性约束...], ADD 字段名 数据类型 [完整性约束...]; ALTER TABLE 表名 ADD 字段名 数据类型 [完整性约束...] FIRST; ALTER TABLE 表名 ADD 字段名 数据类型 [完整性约束...] AFTER 字段名; 3.删除字段 ALTER TABLE 表名 DROP 字段名; 4.修改字段 ALTER TABLE 表名 MODIFY 字段名 数据类型 [完整性约束条件...]; ALTER TABLE 表名 CHANGE 旧字段名 新字段名 新数据类型 [完整性约束条件...];
删除表
DROP TABLE 表名;
5.数据操作
插入数据INSERT
1.插入完整数据(顺序插入) 语法一: INSERT INTO 表名(字段1,字段2,字段3...字段n) VALUES (值1,值2,值3...值n); 语法二: INSERT INTO 表名 VALUES(值1,值2,值3...值n); 2.指定字段插入数据 语法: INSERT INTO 表名 (字段1,字段2,字段3...) VALUES (值1,值2,值3...); 3.插入多条记录 语法: INSERT INTO 表名 VALUES (值1,值2,值3...值n), (值1,值2,值3...值n), (值1,值2,值3...值n); 4.插入查询结果 语法: INSERT INTO 表名 (字段1,字段2,字段3...字段n) SELECT (字段1,字段2,字段3...字段n) FROM 表2 WHERE ...;
更新数据
UPDATE 表名 SET 字段1=值1, 字段2=值2, WHERE CONITION;
删除数据
DELETE FROM 表名
WHERE CONITION;