Python 线程同步与互斥

版权声明:本文为博主原创文章,可以转载,但得注明出处。更多精彩文章请关注微信公众号:gloryroadtrain https://blog.csdn.net/SeeTheWorld518/article/details/49641805

什么是并发?

在操作系统中,指一个时间段内有几个程序都处于已启动到运行结束之间的状态,并且这几个程序都是在同一个处理机上运行的,但任一个时间点却只有一个程序在处理机上执行。
注意并发与并行并不是同一个概念。并发是指一个时间段内同时运行,表示的是一个区间,而并行是指在同一个时间点上都在运行,是一个点,并且并发在同一时间点上只能有一个程序在运行。
在实际应用中,多个线程往往会共享一些数据(如:内存堆栈、串口、文件等),并且线程间的状态和行为都是互相影响的。并发线程的两种关系:同步与互斥。

线程同步与互斥

互斥:线程之间通过对资源的竞争,所产生的相互制约的关系,就是互斥关系。这类线程间主要的问题就是互斥和死锁的问题。

同步:进程之间不是相互排斥的关系,而是相互依赖的关系。换句话说,就是多进程共享同一临界资源时,前一个进程输出作为后一个进程的输入,当第一个进程没有输出时,第二个进程必须等待。因为当多个线程共享数据时,可能会导致数据处理出错,因此线程同步主要的目的就是使并发执行的各线程之间能够有效的共享资源和相互合作,从而使程序的执行具有可再现性。
共享数据指的是并发执行的多个线程间所操作的同一数据资源。
出现共享资源访问冲突的实质就是线程间没有互斥的使用共享资源,也就是说并发执行过程中,某一个线程正在对共享资源访问时,比如写,此时其它的线程就不能访问这个共享数据,直到正在访问它的线程访问结束。避免互斥,我们通过对共享资源进行加锁操作来避免访问冲突。当有线程拿到访问这个共享数据的权限时,就对其加一把锁,这样别的线程由于得不到访问的锁,所以不能访问,直到线程释放了这把锁,其它线程才能访问。
线程将的同步与互斥,是为了保证所共享的数据的一致性。

关于线程互斥的实例:

import threading
import time

data = 0
lock = threading.Lock()#创建一个锁对象

def func() :
  global data
  print "%s acquire lock...\n" %threading.currentThread().getName()
  if lock.acquire() :
    print "%s get lock...\n" %threading.currentThread().getName()
    data += 1 #must lock
    time.sleep(2)#其它操作
    print "%s release lock...\n" %threading.currentThread().getName()

    #调用release()将释放锁
    lock.release()

startTime = time.time()
t1 = threading.Thread(target = func)
t2 = threading.Thread(target = func)
t3 = threading.Thread(target = func)
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()

endTime = time.time()
print "used time is", endTime - startTime

执行结果:

Thread-1 acquire lock...

Thread-1 get lock...
Thread-2 acquire lock...

Thread-3 acquire lock...


Thread-1 release lock...

Thread-2 get lock...

Thread-2 release lock...

Thread-3 get lock...

Thread-3 release lock...

used time is 6.0039999485

上边的实例创建了3个线程t1、t2和t3同步执行,三个线程都访问全局变量data,并改变它的值。当第一个线程t1请求锁成功后,开始访问共享数据data,第二个线程t2和t2也开始请求锁,但是此时t1还没有释放锁,所以t2、t3处于等待锁状态,直到t1调用lock.release()释放锁以后,t2才得到锁,然后执行完释放锁,t3才能得到锁。这样就保证了这三个线程共享数据data的一致性和同步性。并且这三个线程是并发执行的,没有人为控制其获得锁的顺序,所以它们执行的顺序也是不定的。
注意:
调用acquire([timeout])时,线程将一直阻塞,直到获得锁定或者直到timeout秒后返回是否获得锁。

关于线程同步的一个经典实例:生产者与消费者

#coding=utf-8  
from Queue import Queue   #队列类 
import random    
import threading    
import time      

#生成者线程
class Producer(threading.Thread):    
    def __init__(self, t_name, queue): 
        #调用父线程的构造方法。
        threading.Thread.__init__(self, name=t_name)    
        self.data=queue  

    def run(self):    
        for i in range(5):    
            print "%s: %s is producing %d to the queue!\n" %(time.ctime(), self.getName(), i)  
            self.data.put(i)#向队列中添加数据    
            #产生一个0-2之间的随机数进行睡眠
            time.sleep(random.randrange(10)/5)    
        print "%s: %s finished!" %(time.ctime(), self.getName()) 
#消费者线程 
class Consumer(threading.Thread):    
    def __init__(self, t_name, queue):    
        threading.Thread.__init__(self, name=t_name)  
        self.data=queue  

    def run(self):    
        for i in range(5):    
            val = self.data.get()#从队列中取出数据    
            print "%s: %s is consuming. %d in the queue is consumed!\n" %(time.ctime(), self.getName(), val)
            time.sleep(random.randrange(10))  

        print "%s: %s finished!" %(time.ctime(), self.getName())  
#Main thread    
def main():    
    queue = Queue()#创建一个队列对象(特点先进先出)    
    producer = Producer('Pro.', queue)#生产者对象    
    consumer = Consumer('Con.', queue)#消费者对象
    producer.start()    
    consumer.start()  
    producer.join()    
    consumer.join()  
    print 'All threads terminate!'  

if __name__ == '__main__':  
    main()

通过使用Python的队列类,进行线程同步处理时,就不需要考虑加锁的问题了,因为队列内部会自动加锁进行处理。

死锁

如果程序中多个线程相互等待对方持有的锁,而在得到对方的锁之前都不释放自己的锁,由此导致了这些线程不能继续运行,这就是死锁。
死锁的表现是:程序死循环。
防止死锁一般的做法是:如果程序要访问多个共享数据,则首先要从全局考虑定义一个获得锁的顺序,并且在整个程序中都遵守这个顺序。释放锁时,按加锁的反序释放即可。
所以必须是有两个及其以上的的并发线程,才能出现死锁,如果是多于2个线程之间出现死锁,那他们请求锁的关系一定是形成了一个环,比如A等B的锁,B等C的锁,C等A的锁。

实例:

#coding=utf-8
import threading  
import time

lock1 = threading.Lock()  
lock2 = threading.Lock()  
print lock1, lock2
class T1(threading.Thread):  
    def __init__(self, name):  
        threading.Thread.__init__(self)  
        self.t_name = name  

    def run(self):  
        lock1.acquire()  
        time.sleep(1)#睡眠的目的是让线程2获得调度,得到第二把锁
        print 'in thread T1',self.t_name
        time.sleep(2) 
        lock2.acquire() #线程1请求第二把锁  
        print 'in lock l2 of T1'  
        lock2.release()      
        lock1.release() 

class T2(threading.Thread):  
    def __init__(self, name):  
        threading.Thread.__init__(self)  
        self.t_name = name  

    def run(self):  
        lock2.acquire()  
        time.sleep(2)#睡眠的目的是让线程1获得调度,得到第一把锁
        print 'in thread T2',self.t_name
        lock1.acquire() #线程2请求第一把锁
        print 'in lock l1 of T2'
        lock1.release() 
        lock2.release() 

def test():  
    thread1 = T1('A')  
    thread2 = T2('B')  
    thread1.start()  
    thread2.start()  

if __name__== '__main__':  
    test()  

上面的实例中,在两个线程thread1和thread2分别得到一把锁后,然后在线程1中请求线程2得到的那把锁,线程2中请求在线程1中得到的那把锁,由于两个线程都在请求对方的锁,但却没有一方释放它们的锁,所以就会出现死锁的情况,程序执行后就会出现下面的结果:
6

进程与线程的区别

  • 地址空间和其他资源:进程间相互独立,统一进程的各线程间共享。
  • 通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信,但需要通过线程同步和互斥来保证数据的一致性。
  • 调度和切换:线程上下文切换比进程上下文切换要快的多。

猜你喜欢

转载自blog.csdn.net/SeeTheWorld518/article/details/49641805