进程与线程

系统基础

操作系统的作用

  • 隐藏复杂的硬件接口,提供良好的抽象接口。

  • 管理、调度进程,并且将多个进程对硬件的竞争变得有序。

多道技术

  • 产生背景:针对单核,实现并发(现在的主机一般是多核,那么每个核都会利用多道技术,但是核与核之间没有使用多道技术切换这么一说,一个程序io阻塞,会等到io结束再重新调度)

  • 时间上的复用(复用一个cpu的时间片)+空间上的复用(如内存中同时有多道程序)

特点:多道----主存中存放两道或两道以上的程序;宏观上 并行----在一个时间段,它们都在同时执行,都处于执行的开始点和结束点之间;微观上串行----在某一时刻,他们在同一台计算机上交替、轮流、穿插地执行。

进程

进程实体:程序段、数据段、进程控制块(PCB)。

进程:进程就是进程实体的一次执行过程。

多进程(任务)并发执行:比如你现在使用电脑,一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,对于单核CPU来说,执行多任务是这样的:操作系统轮流让各个任务交替执行,任务1执行0.001秒,切换到任务2,任务2执行0.001秒,再切换到任务3,执行0.001秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。

进程并行:对于多核CPU的系统,如果是4核可以有四个任务并行执行。并行执行就是真正的同时执行,所以并行是并发的子集。

总结:现在的操作系统很多任务执行都是并行+并发执行。

线程

有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。

线程的出现是为了降低上下文切换的消耗,提高系统的并发性,并突破一个进程只能干一样事的缺陷,使到进程内并发成为可能。

进程与线程关系

进程是最小的资源管理单元,线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。

资源分配给进程,同一进程的所有线程共享该进程的所有资源。

python中threading模块

python中要在一个程序中开多个线程需要使用threading模块。

线程创建的两种方式

方式一:直接创建

import threading
import time
def foo(n):
    print(">>>>>>>>%s" %n)
    time.sleep(n)
    print(threading.active_count())   #打印当前线程数目,结果为3,sleep后t2早已激活,还有一个是主线程

def bar(n):
    print(">>>>>>>>%s" % n)
    time.sleep(n)
t1 = threading.Thread(target=foo,args=(2,))    #创建一个线程对象,执行foo任务,元组形式传入参数(2,)
t1.start()    #激活线程对象(调用run方法)
print(threading.active_count())   ##打印当前线程数目,结果为2,因为t2还没有激活,还有一个是主线程
t2 = threading.Thread(target=bar,args=(5,))
t2.start()
print("ending")

方式二:从Thread继承,并重写run()

import threading
import time

class MyThread(threading.Thread):

    def __init__(self,num):   #重写init方法,调用父类的init
        # threading.Thread.__init__(self)
        super(MyThread,self).__init__()
        self.num=num

    def run(self):    #重写run方法
        print("running on number:%s" %self.num)
        time.sleep(3)

t1=MyThread(1)
t2=MyThread(21)

t1.start()
t2.start()
print("ending")

Thread类的实例方法

# start():  激活线程
# join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞。

# setDaemon(True):
 '''
 将线程声明为守护线程,必须在start() 方法调用之前设置,如果不设置为守护线程程序会被无限挂起。

 当我们在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成

 想退出时,会检验子线程是否完成。如果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是只要主线程

 完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法啦'''

  # isAlive(): 返回线程是否活动的。
  # getName(): 返回线程名。
  # setName(): 设置线程名。

threading模块提供的一些方法:

  # threading.currentThread(): 返回当前的线程变量。
  # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

GIL锁

Python中的线程是操作系统的原生线程,Python虚拟机使用一个全局解释器锁(Global Interpreter Lock)来互斥线程对Python虚拟机的使用。为了支持多线程机制,一个基本的要求就是需要实现不同线程对共享资源访问的互斥,所以引入了GIL。要知道GIL锁是在进程上的,即一个进程内线程无法在多核系统并行执行。最重要的是Cpython才有的GIL锁,大多数都是默认Cpython

GIL:在一个线程拥有了解释器的访问权之后,其他的所有线程都必须等待它释放解释器的访问权,即使这些线程的下一条指令并不会互相影响。
在调用任何Python C API之前,要先获得GIL

GIL缺点:多处理器退化为单处理器;无法利用多核CPU实现多线程优点:避免大量的加锁解锁操作

线程同步锁

为什么要有同步锁呢?答案是可以解决数据安全一致性问题。如下示例:

#未在数据处理段加锁,想得到最终num=0,执行结果却是94

import threading
import time
num = 100
def foo():
    global num
    temp = num
    time.sleep(0.0001)
    num = temp - 1
l = []
for i in range(100):
    threading.Thread(target=foo,args=()).start()
    # l.append(t)
print(threading.active_count())
while True:
    if threading.active_count() == 1:
        break
# for x in l:
#     x.join()
print(num)

#在数据处理段加锁之后得到理想结果num = 0

import threading
import time
num = 100
lock = threading.Lock()
def foo():
    global num
    lock.acquire()
    temp = num
    time.sleep(0.0001)
    num = temp - 1
    lock.release()
l = []
for i in range(100):
    threading.Thread(target=foo,args=()).start()
    # l.append(t)
print(threading.active_count())
while True:
    if threading.active_count() == 1:
        break
# for x in l:
#     x.join()    #等待所有线程执行完毕
print(num)

当给线程处理内容加锁之后,只有当锁release释放之后其它线程才能去acquire获得这把锁,这样就能保证存取操作的唯一性, 从而保证同一时刻只有一个线程对共享数据存取。一般同步锁使用形式:

import threading

lock=threading.Lock()

lock.acquire()
'''
对公共数据的操作
'''
lock.release()

死锁与递归锁

所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

死锁

#死锁示例:
import threading
import time
lockA = threading.RLock()
lockB = threading.RLock()
class Mythread(threading.Thread):
    def __init__(self):
        super().__init__()
    def run(self):
        self.foo()
        self.bar()
    def foo(self):
        lockA.acquire()
        print('lock A',self.name)
        lockB.acquire()
        print('lock B',self.name)
        lockB.release()
        # time.sleep(0.2)
        lockA.release()
        # time.sleep(0.2)
    def bar(self):
        lockB.acquire()
        print('lock B',self.name)
        lockA.acquire()
        print('lock A',self.name)
        lockA.release()
        lockB.release()
for i in range(10):
    t = Mythread()
    t.start()
#上面这段程序就可能出现死锁,当然也有几率不会出现死锁,出现死锁的情况就是这样:
当线程1的foo释放完B锁后,B锁被线程1的bar函数抢到,然后当线程1
释放A锁后,A锁被线程2的foo函数抢到了,这样就会出现死锁。

lock A Thread-1
lock B Thread-1
lock B Thread-1
lock A Thread-2

递归锁解决死锁现象

import threading
import time
Rlock = threading.RLock()
class Mythread(threading.Thread):
    def __init__(self):
        super().__init__()
    def run(self):
        self.foo()
        self.bar()
    def foo(self):
        Rlock.acquire()
        print('lock A',self.name)
        Rlock.acquire()
        print('lock B',self.name)
        Rlock.release()
        # time.sleep(0.2)
        Rlock.release()
        # time.sleep(0.2)
    def bar(self):
        Rlock.acquire()
        print('lock B',self.name)
        Rlock.acquire()
        print('lock A',self.name)
        Rlock.release()
        Rlock.release()
for i in range(10):
    t = Mythread()
    t.start()

在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁。

信号量锁Semaphore

Semaphore管理一个内置的计数器,

每当调用acquire()时内置计数器-1;

调用release() 时内置计数器+1;

计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

信号量锁示例:

import threading
import time

semaphore = threading.Semaphore(5)   #设置同时可以有5个线程可以获得信号量锁

def func():
    if semaphore.acquire():
        print (threading.currentThread().getName() + ' get semaphore')
        time.sleep(2)
        semaphore.release()

for i in range(20):
  t1 = threading.Thread(target=func)
  t1.start()

#最好执行结果,每两秒并发执行5个线程任务,即每2秒打印5次
posted @ 2017-08-19 23:07  村口王铁匠  阅读(336)  评论(0编辑  收藏  举报