一、进程与线程

1.进程

我们电脑的应用程序,都是进程,假设我们用的电脑是单核的,cpu同时只能执行一个进程。当程序出于I/O阻塞的时候,CPU如果和程序一起等待,那就太浪费了,cpu会去执行其他的程序,此时就涉及到切换,切换前要保存上一个程序运行的状态,才能恢复,所以就需要有个东西来记录这个东西,就可以引出进程的概念了。

进程就是一个程序在一个数据集上的一次动态执行过程。进程由程序,数据集,进程控制块三部分组成。程序用来描述进程哪些功能以及如何完成;数据集是程序执行过程中所使用的资源;进程控制块用来保存程序运行的状态

2.线程

一个进程中可以开多个线程,为什么要有进程,而不做成线程呢?因为一个程序中,线程共享一套数据,如果都做成进程,每个进程独占一块内存,那这套数据就要复制好几份给每个程序,不合理,所以有了线程。

线程又叫轻量级进程,是一个基本的cpu执行单元,也是程序执行过程中的最小单元。一个进程最少也会有一个主线程,在主线程中通过threading模块,在开子线程

3.进程线程的关系

(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程

(2)资源分配给进程,进程是程序的主体,同一进程的所有线程共享该进程的所有资源

(3)cpu分配给线程,即真正在cpu上运行的是线程

(4)线程是最小的执行单元,进程是最小的资源管理单元

4.并行和并发
并行处理是指计算机系统中能同时执行两个或多个任务的计算方法,并行处理可同时工作于同一程序的不同方面

并发处理是同一时间段内有几个程序都在一个cpu中处于运行状态,但任一时刻只有一个程序在cpu上运行。

并发的重点在于有处理多个任务的能力,不一定要同时;而并行的重点在于就是有同时处理多个任务的能力。并行是并发的子集
进程线程协程

以上所说的是相对于所有语言来说的,Python的特殊之处在于Python有一把GIL锁,这把锁限制了同一时间内一个进程只能有一个线程能使用cpu

二、threading模块

这个模块的功能就是创建新的线程,有两种创建线程的方法:

1.直接创建

 
import threading
import time

def foo(n):
    print('>>>>>>>>>>>>>>>%s'%n)
    time.sleep(3)
    print('tread 1')

t1=threading.Thread(target=foo,args=(2,))
#arg后面一定是元组,t1就是创建的子线程对象
t1.start()#把子进程运行起来

print('ending')
 

上面的代码就是在主线程中创建了一个子线程

运行结果是:先打印>>>>>>>>>>>>>2,在打印ending,然后等待3秒后打印thread 1

2.另一种方式是通过继承类创建线程对象

 
import  threading
import time

class MyThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        print('ok')
        time.sleep(2)
        print('end')

t1=MyThread()#创建线程对象
t1.start()#激活线程对象
print('end again')
 

3.join()方法

这个方法的作用是:在子线程完成运行之前,这个子线程的父线程将一直等待子线程运行完再运行

 
import threading
import time

def foo(n):
    print('>>>>>>>>>>>>>>>%s'%n)
    time.sleep(n)

    print('tread 1')
def bar(n):
    print('>>>>>>>>>>>>>>>>%s'%n)
    time.sleep(n)
    print('thread 2')
s=time.time()
t1=threading.Thread(target=foo,args=(2,))
t1.start()#把子进程运行起来

t2=threading.Thread(target=bar,args=(5,))
t2.start()

t1.join()     #只是会阻挡主线程运行,跟t2没关系
t2.join()
print(time.time()-s)
print('ending')
'''
运行结果:
>>>>>>>>>>>>>>>2
>>>>>>>>>>>>>>>>5
tread 1
thread 2
5.001286268234253
ending
'''
 

4.setDaemon()方法

这个方法的作用是把线程声明为守护线程,必须在start()方法调用之前设置。

默认情况下,主线程运行完会检查子线程是否完成,如果未完成,那么主线程会等待子线程完成后再退出。但是如果主线程完成后不用管子线程是否运行完都退出,就要设置setDaemon(True)

 
import  threading
import time

class MyThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        print('ok')
        time.sleep(2)
        print('end')

t1=MyThread()#创建线程对象
t1.setDaemon(True)
t1.start()#激活线程对象
print('end again')
#运行结果是马上打印ok和 end again 
#然后程序终止,不会打印end
 

主线程默认是非守护线程,子线程都是继承的主线程,所以默认也都是非守护线程

5.其他方法

isAlive(): 返回线程是否处于活动中

getName(): 返回线程名

setName(): 设置线程名

threading.currentThread():返回当前的线程变量

threading.enumerate():返回一个包含正在运行的线程的列表

threading.activeCount():返回正在运行的线程数量

三、各种锁

1.同步锁(用户锁,互斥锁)

先来看一个例子:

需求是有一个全局变量的值是100,我们开100个线程,每个线程执行的操作是对这个全局变量减一,最后import threading

 

 
import threading
import time

def sub():

    global num
    temp=num

    num=temp-1
    time.sleep(2)
num=100


l=[]for i in range(100):
    t=threading.Thread(target=sub,args=())
    t.start()
    l.append(t)
for i in l:
    i.join()

print(num)
 

 

好像一切正常,现在我们改动一下,在sub函数的temp=num,和num=temp-1 中间,加一个time.sleep(0.1),会发现出问题了,结果变成两秒后打印99了,改成time.sleep(0.0001)呢,结果不确定了,但都是90几,这是怎么回事呢?

这就要说到Python里的那把GIL锁了,我们来捋一捋:

首次定义一个全局变量num=100,然后开辟了100个子线程,但是Python的那把GIL锁限制了同一时刻只能有一个线程使用cpu,所以这100个线程是处于抢这把锁的状态,谁抢到了,谁就可以运行自己的代码。在最开始的情况下,每个线程抢到cpu,马上执行了对全局变量减一的操作,所以不会出现问题。但是我们改动后,在全局变量减一之前,让他睡了0.1秒,程序睡着了,cpu可不能一直等着这个线程,当这个线程处于I/O阻塞的时候,其他线程就又可以抢cpu了,所以其他线程抢到了,开始执行代码,要知道0.1秒对于cpu的运行来说已经很长时间了,这段时间足够让第一个线程还没睡醒的时候,其他线程都抢到过cpu一次了。他们拿到的num都是100,等他们醒来后,执行的操作都是100-1,所以最后结果是99.同样的道理,如果睡的时间短一点,变成0.001,可能情况就是当第91个线程第一次抢到cpu的时候,第一个线程已经睡醒了,并修改了全局变量。所以这第91个线程拿到的全局变量就是99,然后第二个第三个线程陆续醒过来,分别修改了全局变量,所以最后结果就是一个不可知的数了。一张图看懂这个过程

进程线程协程

这就是线程安全问题,只要涉及到线程,都会有这个问题。解决办法就是加锁

我们在全局加一把锁,用锁把涉及到数据运算的操作锁起来,就把这段代码变成串行的了,上代码:

 
import threading
import time

def sub():

    global num
    lock.acquire()#获取锁
    temp=num
    time.sleep(0.001)

    num=temp-1
    lock.release()#释放锁
    time.sleep(2)
num=100


l=[]
lock=threading.Lock()
for i in range(100):
    t=threading.Thread(target=sub,args=())
    t.start()
    l.append(t)
for i in l:
    i.join()

print(num)
 

获取这把锁之后,必须释放掉才能再次被获取。这把锁就叫用户锁

2.死锁与递归锁

死锁就是两个及以上进程或线程在执行过程中,因相互制约造成的一种互相等待的现象,若无外力作用,他们将永远卡在那里。举个例子:

进程线程协程
 1 import threading,time
 2 
 3 class MyThread(threading.Thread):
 4     def __init(self):
 5         threading.Thread.__init__(self)
 6 
 7     def run(self):
 8 
 9         self.foo()
10         self.bar()
11     def foo(self):
12         LockA.acquire()
13         print('i am %s GET LOCKA------%s'%(self.name,time.ctime()))
14         #每个线程有个默认的名字,self.name就获取这个名字
15 
16         LockB.acquire()
17         print('i am %s GET LOCKB-----%s'%(self.name,time.ctime()))
18 
19         LockB.release()
20         time.sleep(1)
21         LockA.release()
22 
23     def bar(self):#与
24         LockB.acquire()
25         print('i am %s GET LOCKB------%s'%(self.name,time.ctime()))
26         #每个线程有个默认的名字,self.name就获取这个名字
27 
28         LockA.acquire()
29         print('i am %s GET LOCKA-----%s'%(self.name,time.ctime()))
30 
31         LockA.release()
32         LockB.release()
33 
34 LockA=threading.Lock()
35 LockB=threading.Lock()
36 
37 for i in range(10):
38     t=MyThread()
39     t.start()
40 
41 #运行结果:
42 i am Thread-1 GET LOCKA------Sun Jul 23 11:25:48 2017
43 i am Thread-1 GET LOCKB-----Sun Jul 23 11:25:48 2017
44 i am Thread-1 GET LOCKB------Sun Jul 23 11:25:49 2017
45 i am Thread-2 GET LOCKA------Sun Jul 23 11:25:49 2017
46 然后就卡住了
进程线程协程

相关文章: