**
什么是线程?
**
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。(维基百科)
上面的解释很抽象吧,我再解释一下。当一个程序启动时,就会产生一个进程或者多个进程,当一个进程产生,同时也会产生一个进程,这个进程就是主进程。而主进程还会产生子进程,这些子进程是可以同时进行的。
例如,有两个这样的程序:
def a():
print('第一个函数')
def b():
print('第二个函数')
a()
b()
上面的程序先执行函数a(),再执行函数b()。如果要用到进程的话,函数a()和函数b()可以同时进行。
**
python中的进程
**
python提供了threading模块进行进程的调控。我们把上面的那段程序改写成多进程的:
import threading
def a():
print('第一个函数\n')
def b():
print('第二个函数\n')
th1=threading.Thread(target=a)
th2=threading.Thread(target=b)
th1.start()
th2.start()
运行结果:
>>> 第一个函数
第二个函数
也有可能是:
>>> 第二个函数
第一个函数
为什么会出现这种状况,为什么呢?因为函数a()和函数b()是同时启动的,执行时间差不多,所以有可能是a()先执行完,也有可能是b()先执行完。然后,我们再看看进程的形式th1=threading.Thread(target=a),其中th1是子进程的名字,target锁定函数,如果函数有参数怎么办?要写成threading.Thread(target=a,args=(x,x))这种形式。当把函数写进进程后,就要启动,th1.start()
这段程序不太能体现出线程的优点,我们再写一段程序:
import threading,time
def a():
print('a begin!')
print('a is running.........')
time.sleep(2)
print('a end!')
def b():
print('b begin!')
print('b is running.........')
time.sleep(2)
print('b end!')
_time=time.time()
a()
b()
print('共耗时%f秒'%(time.time()-_time))
运行结果:
a begin!
a is running.........
a end!
b begin!
b is running.........
b end!
共耗时4.058232秒
上面的代码没有写成进程,所以共耗时4秒。我们把它写成进程:
import threading,time
def a():
print('a begin!')
print('a is running.........')
time.sleep(2)
print('a end!')
def b():
print('b begin!')
print('b is running.........')
time.sleep(2)
print('b end!')
_time=time.time()
_a=threading.Thread(target=a)
_b=threading.Thread(target=b)
_a.start()
_b.start()
_b.join()
_b.join()
print('共耗时%f秒'%(time.time()-_time))
运行结果:
a begin!b begin!
a is running.........b is running.........
a end!b end!
共耗时2.073119秒
从上面的结果可以看出耗时2秒,节省了一半时间。join()函数是起到阻塞的作用,详细用法见我的博客:python多线程中join和setDaemon的用法。
**
线程中的互斥
**
先写个实例程序:
import threading,time
num=0
def a(n):
global num
num=num+n
num=num-n
def change(n):
for i in range(100000):
a(n)
t1=threading.Thread(target=change,args=(5,))
t2=threading.Thread(target=change,args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(num)
在上面代码中,执行函数change(),根据我们的算法,无论函数的参数是多少,执行多少次,num的结果应该始终是零。但是运行结果如下:
13
或者:
-2
这是为什么呢?num是全局变量,在同一进程下的线程之间的变量是共享的,所以,在t1和t2这两个进程中都会对num进行操作。当执行到线程t1中num=num+你,应该接着执行t1进程中的num=num-1,但是进程都是同时进行的,所以会对num进行线程t2中的操作,num因此会出现其他值。为了避免多个进程同时对一个变量或者文件操作,python引进了互斥锁:当一个进程对一个变量或一个文件进行操作时,会禁止其他进程对此变量或者文件进行操作。我们可以将上面的代码改写如下:
import threading,time
num=0
mylock=threading.Lock()
def a(n):
global num
mylock.acquire()
num=num+n
num=num-n
mylock.release()
def change(n):
for i in range(100000):
a(n)
t1=threading.Thread(target=change,args=(5,))
t2=threading.Thread(target=change,args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(num)
这样执行的结果始终是0。此外,只要加锁就一定要解锁,否则会形成死锁,就是其他进程始终不能对变量进行操作。加锁的位置只要能够将变量包住就行,例如将锁加在如下位置也可以:
def change(n):
for i in range(100000):
mylock.acquire()
a(n)
mylock.release()
**
python中多线程的本质
**
在python中的多线程其实并不能真正利用多核CPU的优势,这是因为在python中有一个GIL(Global Interpreter Lock)。Python在执行多线程程序时,其中一个线程在执行前先获GIL,这样其他的线程就不能执行。当第一个线程执行了若干行代码后再释放GIL,由另外一个线程获得GIL并执行。由此可见,早Python中多线程必不能有效的利用多核CPU的优势。因此为了个好的有效利用多核CPU,所以我们可以创建多个多线程的进程。