python~多线程学习(二)

本文介绍了Python中的多线程,包括并发与并行的区别、线程池的实现、线程同步(互斥、条件变量、信号量、事件)以及如何避免死锁。通过示例代码展示了线程同步的各种方法和死锁的概念。
摘要由CSDN通过智能技术生成

目录

什么是并发?

并发与并行的区别:

线程池(并发)

线程同步与互斥

线程互斥

线程同步(生产者与消费者)

线程同步(Lock-加锁)

线程同步(Semaphore-加锁)

线程同步(Event-信号传递)

线程同步(Condition)

线程同步(消息队列-Queue)

死锁


什么是并发?

在操作系统中,指一个时间段内有几个程序都处于已启动到运行结束之间的状态,并且这几个程序都是在同一个处理机上运行的,但任一个时间点却只有一个程序在处理机上执行。

注意并发与并行并不是同一个概念。并发是指一个时间段内同时运行,表示的是一个区间,而并行是指在同一个时间点上都在运行,是一个点,并且并发在同一时间点上只能有一个程序在运行。

并发与并行的区别:

并发当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态。这种方式我们称之为并发(Concurrent)。

并行:当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。

区别:并发和并行是即相似又有区别的两个概念,并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。在多道程序环境下,并发性是指在一段时间内宏观上有多个程序在同时运行,但在单处理机系统中,每一时刻却仅能有一道程序执行,故微观上这些程序只能是分时地交替执行。倘若在计算机系统中有多个处理机,则这些可以并发执行的程序便可被分配到多个处理机上,实现并行执行,即利用每个处理机来处理一个可并发执行的程序,这样,多个程序便可以同时执行。

在实际应用中,多个线程往往会共享一些数据(如:内存堆栈、串口、文件等),并且线程间的状态和行为都是互相影响的。

并发线程的两种关系:同步与互斥。

线程池(并发)

Python中线程multiprocessing模块与进程使用的同一模块。使用方法也基本相同,唯一不同的是,from multiprocessing import Pool这样导入的Pool表示的是进程池;from multiprocessing.dummy import Pool这样导入的Pool表示的是线程池。这样就可以实现线程并发了。

代码示例:实现线程池并发

#encoding=utf-8

import time
from multiprocessing.dummy import Pool as ThreadPool
#ThreadPool表示给线程池取一个别名ThreadPool

def run(fn):
    time.sleep(2)
    print(fn,end="")

if __name__ == '__main__':
    testFL = [1,2,3,4,5]
    pool = ThreadPool(10)    #创建10个容量的线程池并发执行
    pool.map(run, testFL)
    pool.close()
    pool.join()

***这里的pool.map()函数,跟进程池的map函数用法一样,也跟内建的map函数一样。

线程同步与互斥

互斥:线程之间通过对资源的竞争,所产生的相互制约的关系,就是互斥关系。

同步:线程之间不是相互排斥的关系,而是相互依赖的关系。换句话说,就是多线程共享同一临界资源时,前一个线程输出作为后一个线程的输入,当第一个线程没有输出时,第二个线程必须等待。因为当多个线程共享数据时,可能会导致数据处理出错,因此线程同步主要的目的就是使并发执行的各线程之间能够有效的共享资源和相互合作,从而使程序的执行具有可再现性。

共享数据指的是并发执行的多个线程间所操作的同一数据资源。出现共享资源访问冲突的实质就是线程间没有互斥的使用共享资源,也就是说并发执行过程中,某一个线程正在对共享资源访问时,比如写,此时其它的线程就不能访问这个共享数据,直到正在访问它的线程访问结束。我们通过对共享资源进行加锁操作来避免访问冲突。当有线程拿到访问这个共享数据的权限时,就对其加一把锁,这样别的线程由于得不到访问的锁,所以不能访问,直到线程释放了这把锁,其它线程才能访问。线程的同步与互斥,是为了保证所共享的数据的一致性。

线程互斥

代码示例:

#encoding=utf-8

import threading
import time

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

def func() :
    global data
    print("%s acquire lock...\n" % threading.currentThread().getName())
    if lock.acquire() :   #调用acquire()获得锁
        print("%s get lock...\n" %threading.currentThread().getName())
        data += 1  
        time.sleep(2)
        print("%s release lock...\n" %threading.currentThread().getName())
        lock.release()   #调用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()
print (data)
endTime = time.time()
print("used time is", endTime -startTime)

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

***调用acquire([timeout]),线程将一直阻塞,直到获得锁或者直到timeout秒后返回是否获得锁。

线程同步(生产者与消费者)

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)   #向队列中添加数据
            time.sleep(random.randrange(10) / 5)   #产生一个0-2之间的随机数进行睡眠
        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()

输出结果:

线程同步(Lock-加锁)

#encoding=utf-8
from threading import Thread, Lock
import threading

def run(lock, num):
    lock.acquire()    # 获得锁
    threadName = threading.current_thread().getName()    # 取得当前线程的线程名
    print("%s, Hello Num: %s" %(threadName, num))
    lock.release()    # 释放锁

if __name__ == '__main__':
    lock = Lock() # 创建一个共享锁实例
    for num in range(10):
        Thread(name = 'Thread-%s' %str(num), target = run, args = (lock,num)).start()

输出结果:

线程同步(Semaphore-加锁)

#encoding=utf-8
from threading import Thread, Lock
import threading
import time

def worker(s, i):
    s.acquire()
    print(threading.current_thread().name + " acquire")
    time.sleep(i)
    print(threading.current_thread().name + " release")
    s.release()

if __name__ == "__main__":
    # 设置限制最多3个线程同时访问共享资源,当有线程锁被释放后,其他线程才会被执行
    s = threading.Semaphore(3)
    for i in range(5):
        t = Thread(target = worker, args = (s, i * 2))
        t.start()

输出结果:

线程同步(Event-信号传递)

#encoding=utf-8

from threading import Thread, Lock
import threading
import time

def wait_for_event(e):
    """Wait for the event to be set before doing anything"""
    print('wait_for_event: starting')
    e.wait() # 等待收到能执行信号,如果一直未收到将一直阻塞
    print('wait_for_event: e.is_set()->', e.is_set())

def wait_for_event_timeout(e, t):
    """Wait t seconds and then timeout"""
    print('wait_for_event_timeout: starting')
    e.wait(t)# 等待t秒超时,此时Event的状态仍未未设置
    print('wait_for_event_timeout: e.is_set()->', e.is_set())
    e.set()# 设置Event的状态为True

if __name__ == '__main__':
    e = threading.Event()
    print("begin, e.is_set()", e.is_set())    # 初始状态为False
    w1 = Thread(name = 'block', target = wait_for_event, args = (e,))
    w1.start()
    w2 = Thread(name = 'nonblock', target = wait_for_event_timeout,args = (e, 2))
    w2.start()
    print('main: waiting before calling Event.set()')
    time.sleep(3)
    # e.set()
    print('main: event is set')

输出结果:

线程同步(Condition)

#encoding=utf-8

import threading as tr
import time

def consumer(cond):
    with cond:
        print("%s consumer before wait" % tr.current_thread().name)
        cond.wait() # 等待消费
        print("%s consumer after wait"  % tr.current_thread().name)

def producer(cond):
    with cond:
        print("producer before notifyAll")
        cond.notify_all() # 通知消费者可以消费了
        print("producer after notifyAll")

if __name__ == '__main__':
    condition = tr.Condition()
    t1 = tr.Thread(name = "thread-1", target= consumer, args=(condition,))
    t2 = tr.Thread(name = "thread-2", target= consumer, args=(condition,))
    t3 = tr.Thread(name = "tjread-3", target= producer, args=(condition,))
    t1.start()
    time.sleep(2)
    t2.start()
    time.sleep(2)
    t3.start()

输出结果:

线程同步(消息队列-Queue)

#encoding=utf-8

from threading import Thread
from queue import Queue
import time

# 储钱罐
def create(queue):
    for i in [100, 50, 20, 10, 5, 1, 0.5]:
        if not queue.full():    #判断队列是否已满
            queue.put(i)        #入队列
            print('Put %sRMB to queue.' % i)
            time.sleep(1)

# 取储钱罐中的零钱花
def get(queue):
    while True:
        if not queue.empty():   #判断队列是否为空
            print('Get %sRMB from queue.' % queue.get())
            time.sleep(2)
        else:
            break

if __name__=="__main__":
    q = Queue(5)  # 创建一个队列实例,队列中最多放5个
    create_t = Thread(target=create, args=(q,))
    get_t = Thread(target=get, args=(q,))
    create_t.start()
    get_t.start()
    create_t.join()
    get_t.join()

输出结果:

死锁

如果程序中多个线程相互等待对方持有的锁,而在得到对方的锁之前都不释放自己的锁,由此导致了这些线程不能继续运行,这就是死锁。死锁的表现是:程序死循环。

防止死锁一般的做法是:如果程序要访问多个共享数据,则首先要从全局考虑定义一个获得锁的顺序,并且在整个程序中都遵守这个顺序。释放锁时,按加锁的反序释放即可。所以必须是有两个及其以上的的并发线程,才能出现死锁,如果是多于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()


#正确示范
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)    #锁1执行操作完毕后立刻释放
        lock1.release()
        lock2.acquire() #线程1请求第二把锁
        print('in lock l2 of T1')
        lock2.release()
        print ('thread T1 end ',self.t_name)


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)
        lock2.release()
        lock1.acquire()  # 线程2请求第一把锁
        print('in lock l1 of T2')
        lock1.release()
        print ('thread T2 end ',self.t_name)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值