第十篇:线程
本篇内容
- 开启线程的方式
- 线程pid
- 线程其他方法
- 守护线程
- GIL
- 互斥锁
- Event
- 线程池
一、 开启线程的方式
开启线程的方式分为两种:
(1)利用模块开启线程:
#!/usr/binl/env python #encoding: utf-8 #author: YangLei from threading import Thread def f1(): print("f1 is running") if __name__ == '__main__': t = Thread(target=f1,) t.start() print("主")
(2)利用类开启线程:
#!/usr/binl/env python #encoding: utf-8 #author: YangLei from threading import Thread class CustomThread(Thread): def __init__(self, name): super().__init__() self.name = name def run(self): print('%s is running' % self.name) if __name__ == '__main__': t = CustomThread("yanglei") t.start() print("主")
总结:由执行过程可以看出,线程的开启速度比进程开启的速度快。
二、线程pid
同一个进程中,开启的线程pid相同。
#!/usr/binl/env python #encoding: utf-8 #author: YangLei from threading import Thread import os def f1(): print("%s is running" % os.getpid()) if __name__ == '__main__': t1 = Thread(target=f1,) t2 = Thread(target=f1,) t1.start() t2.start() print("主", os.getpid())
由此可见,同一个进程,线程pid相同,那么我们是不是可以大胆猜测,同一个进程中的线程资源是共享的,下面一段代码就来验证我们的猜想。
#!/usr/binl/env python #encoding: utf-8 #author: YangLei from threading import Thread input_info_list = [] format_list = [] def f1(): while True: input_info = input(">>>: ").strip() input_info_list.append(input_info) def format(): while True: if input_info_list: data = input_info_list.pop() format_list.append(data.upper()) def save(): while True: if format_list: data = format_list.pop() with open("save_data.txt", "a") as f: f.write("%s\n" % data) if __name__ == '__main__': t1 = Thread(target=f1) t2 = Thread(target=format) t3 = Thread(target=save) t1.start() t2.start() t3.start()
三、线程其他方法
current_thread:
该方法的的结果可以让我们得到一个MainThread对象,而getName函数可以使我们获得该线程的线程名。
#!/usr/binl/env python #encoding: utf-8 #author: YangLei from threading import Thread, current_thread import time def f1(): print('%s is running' % current_thread().getName()) time.sleep(2) if __name__ == '__main__': t1 = Thread(target=f1) t2 = Thread(target=f1) t3 = Thread(target=f1) t1.start() t2.start() t3.start() print(current_thread())
四、守护线程
守护线程跟守护进程不同,因线程开启的速度快,所以线程一定会被开启,且如果主进程运行的时间大于守护线程的时间,该守护线程也可以正常运行结束。
例如:
#!/usr/binl/env python #encoding: utf-8 #author: YangLei from threading import Thread import time def f1(): print("f1") time.sleep(3) print("f1 is done") def f2(): print("f2") time.sleep(10) print("f2 is done") if __name__ == '__main__': t1 = Thread(target=f1) t2 = Thread(target=f2) t1.daemon=True t1.start() t2.start() print('主')
五、GIL
1.定义:
在调用外部代码(如C/C++扩展函数)的时候,GIL 将会被锁定,直到这个函数结束为止(由于在这期间没有Python 的字节码被运行,所以不会做线程切换)。
2.设计理念:
六、互斥锁
1.定义:
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
#!/usr/binl/env python #encoding: utf-8 #author: YangLei from threading import Thread, Lock import time count = 50 def work(): global count mutex.acquire() tmp = count time.sleep(0.1) count = tmp - 1 mutex.release() if __name__ == '__main__': mutex = Lock() l = [] start = time.time() for i in range(50): t = Thread(target=work) l.append(t) t.start() for t in l: t.join() print("run time:%s value:%s" % (time.time()-start, count))
2.互斥锁与join的区别:
根据刚才的例子,引发了我们的猜想,就刚才的例子来说,互斥锁跟join基本上并无太大的区别,实则不然。因这段代码特殊,写的也比较短,这几行全是运算操作,如果在运算操作之前还有其他操作,那么它俩的区别就会体现出来了。互斥锁会明显比join运行的速度快。
(1)互斥锁:
#!/usr/binl/env python #encoding: utf-8 #author: YangLei from threading import Thread, Lock import time count = 50 def work(): time.sleep(0.05) global count mutex.acquire() tmp = count time.sleep(0.1) count = tmp - 1 mutex.release() if __name__ == '__main__': mutex = Lock() l = [] start = time.time() for i in range(50): t = Thread(target=work) l.append(t) t.start() for t in l: t.join() print("run time:%s value:%s" % (time.time()-start, count))
(2)join:
#!/usr/binl/env python #encoding: utf-8 #author: YangLei from threading import Thread import time count = 50 def work(): time.sleep(0.05) global count tmp = count time.sleep(0.1) count = tmp - 1 if __name__ == '__main__': start = time.time() for i in range(50): t = Thread(target=work) t.start() t.join() print('run time:%s value:%s' %(time.time()-start, count))
七、Event
Python提供了Event对象用于线程间通信,它是由线程设置的信号标志,如果信号标志位为假,则线程等待直到信号被其他线程设置成真。这一点似乎和windows的event正好相反。 Event对象实现了简单的线程通信机制,它提供了设置信号,清除信号,等待等用于实现线程间的通信。
(1)设置信号
使用Event的set()方法可以设置Event对象内部的信号标志为真。Event对象提供了isSet()方法来判断其内部信号标志的状态,当使用event对象的set()方法后,isSet()方法返回真。
(2)清除信号
使用Event对象的clear()方法可以清除Event对象内部的信号标志,即将其设为假,当使用Event的clear方法后,isSet()方法返回假。
(3)等待
Event对象wait的方法只有在内部信号为真的时候才会很快的执行并完成返回。当Event对象的内部信号标志位假时,则wait方法一直等待到其为真时才返回。
#!/usr/binl/env python #encoding: utf-8 #author: YangLei from threading import Thread,current_thread,Event import time event = Event() def conn_mysql(): count = 1 while not event.is_set(): if count > 3: raise ConnectionError('链接失败') print('%s 等待第%s次链接mysql' % (current_thread().getName(), count)) event.wait(0.5) count += 1 print('%s 链接ok' % current_thread().getName()) def check_mysql(): print('%s 正在检查mysql状态' % current_thread().getName()) time.sleep(1) event.set() if __name__ == '__main__': t1 = Thread(target=conn_mysql) t2 = Thread(target=conn_mysql) check = Thread(target=check_mysql) t1.start() t2.start() check.start()
八、线程池
用ThreadPoolExecutor与用ProcessPoolExecutor看起来没什么区别,只是改了一下签名而已。
不难看出,不管是使用队列还是使用进/线程池,从多进程转化到多线程是十分容易的——仅仅是修改了几个签名而已。当然内部机制完全不同,只是python的封装非常好,使我们可以不用关心这些细节,这正是python优雅之处。
#!/usr/binl/env python #encoding: utf-8 #author: YangLei import requests import os,time,threading from concurrent.futures import ThreadPoolExecutor def get_page(url): print("<%s> get :%s" % (threading.current_thread().getName(), url)) respone = requests.get(url) if respone.status_code == 200: return {"url": url, "text": respone.text} def parse_page(obj): dic = obj.result() print("<%s> parse :%s" % (threading.current_thread().getName(), dic["url"])) time.sleep(0.5) res = "url:%s size:%s\n" % (dic["url"], len(dic["text"])) with open("db.txt", "a") as f: f.write(res) if __name__ == '__main__': p = ThreadPoolExecutor(3) urls = [ "http://www.baidu.com", "http://www.baidu.com", "http://www.baidu.com", "http://www.baidu.com", "http://www.baidu.com", "http://www.baidu.com", "http://www.baidu.com", "http://www.baidu.com", ] for url in urls: p.submit(get_page,url).add_done_callback(parse_page) p.shutdown() print("主进程pid:", os.getpid())