一、多線程
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;