进程是系统进行资源分配最小单元,线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.进程在执行过程当中拥有独立的内存单元,而多个线程共享内存等资源。html
import threading from threading import Thread def test(x): print('this is {}'.format(x)) time.sleep(2) def get_thread(number=5): l_thread = (Thread(target=test, args=(i,)) for i in range(number)) for t in l_thread: print(t) t.start() # 启动线程开始执行 print(len(threading.enumerate())) if __name__ == '__main__': get_thread(5) # 结果 <Thread(Thread-1, initial)> this is 0 <Thread(Thread-2, initial)> this is 1 <Thread(Thread-3, initial)> this is 2 <Thread(Thread-4, initial)> this is 3 <Thread(Thread-5, initial)> this is 4 6
经过以上可知,咱们只须要建立一个Thread对象,并运行start方法,解释器就会建立一个子进程执行咱们的target,咱们建立了5个线程,可是使用threading.enumerate查看线程的数量发现有6个线程,由于当前在执行的还有一个主线程。主线程会默认等待全部的子线程结束后再结束。ide
import threading from threading import Thread class MyThread(Thread): def __init__(self, x): super().__init__() self.x = x def run(self): print('this is {}'.format(self.x)) time.sleep(2) def get_thread1(number=5): l_thread = (MyThread(i) for i in range(number)) for t in l_thread: print(t.name) t.start() print(len(threading.enumerate())) if __name__ == '__main__': get_thread1(5)
Thread对象有一个run方法,它就是咱们须要执行的目标函数,因此咱们能够经过继承Thread对象,重写run方法,将咱们的目标代码放置在run方法中。函数
class Thread: def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None): pass # Thread类是python用来建立线程的类, group:扩展保留字段; target:目标代码,通常是咱们须要建立线程执行的目标函数。 name:线程的名字,若是不指定会自动分配一个; args:目标函数的普通参数; kwargs:目标函数的键值对参数; daemon:设置线程是否为守护线程,便是前台执行仍是后台执行,默认是非守护线程,当daemon=True时,子线程为守护线程,此时主线程不会等待子线程,若是主线程完成会强制杀死全部的子线程而后退出。 # 方法 start():建立一个子线程并执行,该方法一个Thread实例只能执行一次,其会建立一个线程执行该类的run方法。 run():子线程须要执行的代码; join():主线程阻塞等待子线程直到子线程结束才继续执行,能够设置等待超时时间timeout. ident():线程标识符,线程未启动以前为None,启动后为一个int; is_alive():查看子线程是否还活着你返回一个布尔值。 daemon:判断是不是守护线程;
多个线程之间能够共享内存等资源,使得多个线程操做同一份资源的时候可能致使资源发生破坏,即线程非安全。ui
number = 100 class MyThread(Thread): def run(self): for i in range(1000000): global number number += 1 print(number) def get_thread1(number=5): l_thread = (MyThread() for i in range(number)) for t in l_thread: t.start() if __name__ == '__main__': get_thread1(5) # 结果 1439426 1378835 2241060 2533150 3533150
上例可知,若是是同步运算的话,最终number的结果应该为5000100,但显然不是。缘由是若是线程1取得number=100时,线程切换到线程2,又取得number=100,加1赋值给number=101;若是,又切换回线程1,number加1也是101;至关于执行了两次加1的操做,然而number=101.这就是多线程的线程非安全!
怎么解决这个问题呢?咱们看到上述代码中number += 1是核心代码,这个地方随意切换线程就会形成数据破坏,所以只要咱们可以设置代码每次执行到这里的时候不容许切换线程就好了。这就是锁的由来。
用锁加入上述代码:
number = 100 mutex = threading.Lock() # 建立锁对象 class MyThread(Thread): def run(self): global number for i in range(1000000): y = mutex.acquire() # 获取锁 if y: # 拿到锁就执行下面 number += 1 mutex.release() # 释放锁 print(number) def get_thread1(number=5): l_thread = (MyThread() for i in range(number)) for t in l_thread: t.start() if __name__ == '__main__': get_thread1(5) # 结果: 4481177 4742053 4869413 4973771 5000100
可知最后的结果符合预期,threading模块中定义了Lock类,能够很方便实现锁机制,每次执行核心代码以前先去获取锁,拿到了才能执行,拿不到默认阻塞等待。
#建立锁 mutex = threading.Lock() #锁定 mutex.acquire(blocking=True) # blocking=True,默认线程阻塞等待;若是blocking=False,线程不会等待,即上例中y会返回False,继续执行下面的代码,最后的结果不会符合预期 #释放 mutex.release()
加锁以后,锁住的那段代码变成了单线程,阻止了多线程并发执行,效率降低了;
锁能够有多个,若是不一样的线程持有不一样的锁并相互等待的话,就会形成死锁;
python的多线程问题远不止如此,还有一个历史遗留问题-全局锁。
若是一段代码存在两个锁的话,可能会出现死锁现象,一旦出现死锁,系统就会卡死。
number = 100 mutex1 = threading.Lock() # 建立锁对象 mutex2 = threading.Lock() class MyThread1(Thread): def run(self): global number for i in range(1000): if mutex1.acquire(): # 拿到锁就执行下面 number += 1 if mutex2.acquire(): print('this is mutex2') mutex2.release() mutex1.release() # 释放锁 print(number) class MyThread2(Thread): def run(self): global number for i in range(1000): if mutex2.acquire(): # 拿到锁就执行下面 number += 1 if mutex1.acquire(): print('this is mutex2') mutex1.release() mutex2.release() # 释放锁 print(number) def get_thread1(): l_thread = (MyThread1(), MyThread2()) for t in l_thread: t.start() if __name__ == '__main__': get_thread1()
通常解决死锁的办法是尽可能不使用多个锁,或设计程序时避免死锁,或为锁添加超时等待。
全局锁的前世此生不是一两句话能讲完的。可参考:Python全局解释器锁
总结一下就是:
number = 100 number1 = 100 mutex = threading.Lock() class MyThread(Thread): def run(self): global number t1 = time.time() for i in range(1000000): y = mutex.acquire() # 获取锁 if y: # 拿到锁就执行下面 number += 1 mutex.release() # 释放锁 t2 = time.time() print(t2-t1) def get_thread1(number=5): l_thread = (MyThread() for i in range(number)) for t in l_thread: t.start() def get_thread2(n=5): global number1 for i in range(1000000*n): number1 += 1 print(number1) if __name__ == '__main__': get_thread1() t2 = time.time() get_thread2() t3 = time.time() print(t3-t2)
可知多线程的执行时间远远大于单线程。
python最好避免使用多线程,而用多进程代替多线程;
协程是多线程的很好的替代方案。
参考: