线程

线程

在一个进程内可以使用多个线程,每个线程执行不同的任务。
如QQ进程,其下可以同时进行收发信息、播放音乐、查看空间,这些都是用线程并发实现的。

特点
1.一个进程中可以并发多个线程执行不同的任务。
2.线程可以共享进程的资源。
3.线程之间可以共享数据。
4.线程是包含在进程中的,线程崩溃会引发进程崩溃,从而导致该进程其他线程也崩溃。

创建线程

可使用两种方式创建线程。

1.threading模块Thread类创建线程。
2.使用Thread子类创建线程。

threading模块Thread类创建线程

语法

Thread([group [,target [,name [,args [,kwargs]]]]])
'''
group:值为None,该参数未被使用。
target:表示可调用对象,可以为线程需要执行的函数,默认值为None,表示不调用任何内容。
name:当前线程名称,默认为Thread-n
args:传递给target函数的参数,是一个元组。
kwargs:传递给target函数的参数,是一个字典。
'''

案例

import threading
import time

def thread():
    for i in range(3):
        time.sleep(1)
        print('thread name is %s' %threading.current_thread().name)


if __name__ == '__main__':
    print('主线程启动,创建4个线程')
    #使用列表生成式创建4个线程
    threads = [threading.Thread(target=thread) for i in range(4)]
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    print('主进程结束')

'''
out:
主线程启动,创建4个线程
thread name is Thread-2
thread name is Thread-1
thread name is Thread-4
thread name is Thread-3
thread name is Thread-3
thread name is Thread-1
thread name is Thread-2
thread name is Thread-4
thread name is Thread-2
thread name is Thread-4
thread name is Thread-1
thread name is Thread-3
主线程结束
'''
#注意每个线程执行的顺序是乱序的。

使用Thread子类创建线程

Thread线程类跟Process进程类使用方式相似。

案例

from threading import Thread
import time

class SubThread(Thread):
    def run(self):
        for i in range(3):
            time.sleep(1) #因为执行的太快所以等待1秒钟
            msg = '子线程'+self.name+'正在执行任务,此时i='+str(i)
            print(msg)

if __name__ == '__main__':
    print('主线程启动,安排两个线程工作')
    t1 = SubThread() #这里没有传递参数,所以没有创建__init__()方法进行赋值。
    t2 = SubThread()
    t1.start() #开启线程,会自动调用执行run()方法
    t2.start()
    t1.join()
    t2.join()
    print('主线程结束')

'''
out:
主线程启动,安排两个线程工作
子线程Thread-2正在执行任务,此时i=0
子线程Thread-1正在执行任务,此时i=0
子线程Thread-2正在执行任务,此时i=1
子线程Thread-1正在执行任务,此时i=1
子线程Thread-2正在执行任务,此时i=2
子线程Thread-1正在执行任务,此时i=2
主线程结束
'''
#两个子进程是同时开始执行任务,所以i有两次都相等

线程间通信

进程跟进程之间不能直接通信,需要队列或管道当作中间人让进程之间互相通信。
线程之间可以直接进行通信。

案例

from threading import Thread

#定义全局变量使其在进程之间传递
g_num = 100

def plus():
    print('子线程1开始执行任务')
    global g_num
    g_num += 1
    print('全局变量g_num初始值为100,加1,此时为 %d' % g_num)
    print('子线程1结束')

def minus():
    print('子线程2开始执行任务')
    global g_num
    g_num -= 1
    print('全局变量g_num初始值为100,减1,此时为 %d' % g_num)
    print('子线程2结束')

if __name__ == '__main__':
    print('主线程启动,产生两个线程执行任务')
    p1 = Thread(target=plus)
    p2 = Thread(target=minus)
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print('主线程结束')

'''
out:
主线程启动,产生两个线程执行任务
子线程1开始执行任务
全局变量g_num初始值为100,加1,此时为 101
子线程1结束
子线程2开始执行任务
全局变量g_num初始值为100,减1,此时为 100
子线程2结束
主线程结束
'''
#可以看到,全局变量被进程1加1,成为101,被进程2减1,成为100,两者共享使用了全局变量g_num

互斥锁

给线程加锁,当线程使用某块资源时先加锁,使用完后再解锁,锁定期间其他线程不能修改此块资源。
使用互斥锁能够防止多个线程同时读写内存中的某个区域,从而造成混乱。

使用互斥锁
使用threading模块中使用Lock类实现。

Lock类有两个方法:
acquire()锁定
release()解锁

语法

m = threading.Lock() #创建锁对象
m.acquire([blocking]) #锁定
m.release() #解锁
'''
acquire([blocking]):给线程加锁,让其放心的读写某块资源,如果设置blocking参数为Flase,则无法获取锁定状态时会返回False,成功锁定会返回True
release():解锁,与锁定配套使用,如果该线程未被成功锁定,其他线程调用此方法会报错。
'''

案例

#用线程模仿10个人购买电影票
from threading import Thread,Lock
import time

#定义全局变量电影票100张
g_n = 100

def task():
    global g_n
    #上锁
    mutex.acquire()
    time.sleep(1)
    g_n -= 1
    print('购买成功,剩余%d张电影票' % g_n)
    mutex.release()

if __name__ == '__main__':
    mutex = Lock()
    t_list = []
    #创建10个线程,这10个线程相当于10个人,将创建的线程对象放入列表
    for i in range(10):
        t = Thread(target=task)
        t_list.append(t)
        t.start()
    #从列表中取出线程对象,让其进程等待线程执行完毕
    for t in t_list:
        t.join()

'''
out:
购买成功,剩余99张电影票
购买成功,剩余98张电影票
购买成功,剩余97张电影票
购买成功,剩余96张电影票
购买成功,剩余95张电影票
购买成功,剩余94张电影票
购买成功,剩余93张电影票
购买成功,剩余92张电影票
购买成功,剩余91张电影票
购买成功,剩余90张电影票
'''

死锁

使用互斥锁时,要避免死锁。
死锁通俗讲解就是进程A锁住了资源1并等待使用资源2,而进程B锁住了资源2并等待使用资源1,造成了互相等待对方。

使用队列实现线程之间通信

通过queue模块的Queue来实现。

生产者消费者模式
线程间通信通常应用于生产者消费者模式,生产数据的函数或模块称为生产者,处理数据的函数或模块称为消费者,生产者(函数)向仓库(队列)放入商品,消费者从仓库取出商品。

案例

from queue import Queue
from threading import Thread
import time
import random

#定义生产者类
class Producer(Thread):
    def __init__(self,name,queue):
        #调用父类的init方法
        Thread.__init__(self,name=name)
        self.data = queue

    def run(self):
        #定义五个生产者
        for i in range(5):
            print('生产者%s 将产品%d加入队列' %(self.getName(),i))
            #将i加入队列
            self.data.put(i)
            time.sleep(random.random())
        print('生产者%s完成任务!'%(self.getName()))

#定义消费者类
class Consumer(Thread):
    def __init__(self,name,queue):
        #调用父类的init方法
        Thread.__init__(self,name=name)
        self.data = queue

    def run(self):
        #定义五个生产者
        for i in range(5):
            #取出数据
            value = self.data.get()
            print('消费者%s 将产品%d从队列中取出' %(self.getName(),value))
            time.sleep(random.random())
        print('消费者%s完成任务!'%(self.getName()))


if __name__ == '__main__':
    print('主线程开始')
    queue = Queue()
    #创建生产者类对象
    producer = Producer('Producer',queue)
    consumer = Consumer('Consumer',queue)
    producer.start()
    consumer.start()
    producer.join()
    consumer.join()
    print('主线程结束')
'''
out:
主线程开始
生产者Producer 将产品0加入队列
消费者Consumer 将产品0从队列中取出
生产者Producer 将产品1加入队列
消费者Consumer 将产品1从队列中取出
生产者Producer 将产品2加入队列
生产者Producer 将产品3加入队列
消费者Consumer 将产品2从队列中取出
消费者Consumer 将产品3从队列中取出
生产者Producer 将产品4加入队列
消费者Consumer 将产品4从队列中取出
消费者Consumer完成任务!
生产者Producer完成任务!
主线程结束
'''

学习来自:《python从入门到项目实践》明日科技 第十六章
posted @ 2020-12-03 00:23  努力吧阿团  阅读(56)  评论(0编辑  收藏  举报