python-网络编程-线程

目录

1. 初识多线程

1.1 使用threading创建线程

1.2 使用Thread子类创建线程 

 2.线程间的通信

线程间的通信: 

 2.1 什么是互斥锁

2.2 线程间的通信(通过队列)

 

3. 线程与进程的区别

 


1. 初识多线程

课前预习

_thread(低级模块) 和 threading(高级模块,他是对_thraed的封装)

 

 

 

进程:数学题        线程:解题方法 

1.1 使用threading创建线程

threading提供了Thread(模块)这个方法创建一个线程对象

import threading,time  #threading即线程

#一个线程函数
def process():
    for i in range(3):
        time.sleep(1)
        print("线程的名字是:%s"%(threading.current_thread().name)) #threading.current_thread() 方法返回一个线程

if __name__ == '__main__':
    print("---主线程开始---")
    #创建4个线程,并且将其存入列表中
    thteads = [threading.Thread(target=process) for i in range(4)]  #threading.Thread(target=process) 创建线程
    #启动线程
    print("启动线程")
    for t in thteads:
        t.start()
    #结束线程
    print("结束线程")
    for t in thteads:
        t.join()
    print("---主线程结束---")

以上代码创建了4个线程,然后分别用for循环执行start()和join()方法,每个子线程分别执行输出三次。所以定义的process函数的作用就在于规范线程的执行状况。 

thteads = [threading.Thread(target=process) for i in range(4)]  

这一步中的 threading.Thread(target=process) 其实就是直接实例化一个线程对象,而threading.Thread即为创建线程的模块,相当于我们容易理解的:

t = threading.Thread(target=process) # t 就是实例化的线程对象  

类比进程中使用Process创建进程对象。

而对于函数:

#一个线程函数
def process():
    for i in range(3):
        time.sleep(1)
        print("线程的名字是:%s"%(threading.current_thread().name)) #threading.current_thread() 方法返回一个线程

就是编写自己对所创建的线程的要求和规范,作用类似于threading.Thread模块中的run()方法,所以下文中的通过继承threading.Thread类,创建SubThread子类,然后利用SubThread子类构建线程的方式,就省去了当前代码中再“特意”去编写一个def process函数规范线程的操作。(要求每个线程都执行3次) 

问题:线程与进程之间有什么区别与联系? 

1.2 使用Thread子类创建线程 

 通过继承来创建线程,联系的进程中使用Process。

import threading,time

#继承threading.Thread模块,自定义一个线程子类
class SubThreads(threading.Thread):

    #重写threading.Thread中关于线程的运行方法run();使得每个线程执行三遍
    def run(self):
        for i in range(3):
            time.sleep(1) #休眠1s
            msg = ("子线程"+self.name+"现在执行第%s次"%str(i))
            print(msg)

if __name__ == '__main__':
    print("---主线程开始执行---")
    t1 = SubThreads() #实例化线程t1
    t2 = SubThreads()
    print("子线程开始启动...")
    t1.start() #启动子线程t1
    t2.start()
    t1.join() #等待子线程结束
    t2.join()
    print("---主线程结束---")

 如此便把1.1中def process的规范重写到run()方法中。

注:

(1)当用1.1中自定义一个函数(如def lkw)以“规范或者说限制”进程或线程时,其实例化对象为: 

t = Process(target=lkw)

(2)通过继承改写父类Process(threading.Thread)方法,由子类直接实例化对象,即:

t = Subxxx()

 2.线程间的通信

 

线程间的通信: 

from threading import Thread
import time

#规范一个子线程1
def pluse():
    print("---子线程1开始执行---")
    global g_num #定义一个全局变量
    g_num += 50
    print("执行子线程1,使g_num的值变为:%s"%(g_num))
    print("---子线程1结束---")

#规范一个子线程2
def minse():
    print("---子线程2开始执行---")
    global g_num #定义一个全局变量
    g_num += 50
    print("执行子线程2,使g_num的值变为:%s"%(g_num))
    print("---子线程2结束---")

g_num = 100 #定义一个全局变量
if __name__ == '__main__':
    print("---主线程启动---")
    print("初始时g_num的值为:%s"%(g_num))
    t1 = Thread(target=pluse) #实例化子线程1
    t2 = Thread(target=minse)
    print("---子线程启动---")
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print("---主线程结束---")

进程间的通信:

from multiprocessing import Process

#子进程1
def plus():
    print("---子进程1开始执行---")
    global g_num #声明全局变量
    g_num += 50
    print("在子进程1下:g_num = %d"%(g_num))
    print("---子进程1结束运行---")

#子进程2
def minus():
    print("---子进程2开始执行---")
    global g_num #声明全局变量
    g_num -= 50
    print("在子进程2下:g_num = %d"%(g_num))
    print("---子进程2结束运行---")

g_num = 100 #赋值全局变量
if __name__ == '__main__':
    print("---主进程启动---")
    print("在主进程运行中,g_num = %d"%(g_num))
    child1 = Process(target=plus) #实例化子进程1
    child2 = Process(target=minus)  # 实例化子进程2
    child1.start() #启动子进程1
    child2.start()

    child1.join() #等待子进程结束
    child2.join()
    print("---主进程结束---")

 对比如上两个程序可知,对于全局变量,子线程之间可以实现共享,而进程则不行。

 2.1 什么是互斥锁

如上所诉,线程之间可以相互通信(共享全局变量),那么在执行多线程任务的时候,由于大家都可以共享和修改全局变量,就容易照成数据混乱,互斥锁就是解决这一问题而存在的。 

 例子:排队上卫生间,进去的人锁门,其他人排队等候。yue~

以电影院买票为例子:本场电影100张票 10个用户同时抢购电影票,每售出一张,显示剩余电影票。 

程序如下:

from threading import Thread,Lock #导入线程和互斥锁模块
import time

ticket_num = 100 #初始化100张电影票
mutex = Lock() #实例化一个互斥锁

#规定子线程的执行方式(此处模拟买电影票的过程)
def buy_ticket():
    global ticket_num #声明ticket_num为全局变量
    mutex.acquire() #锁定 
    temp = ticket_num
    time.sleep(0.2)
    ticket_num = temp - 1
    print("购票成功,剩余%s张电影票。"%(ticket_num))
    mutex.release() #释放锁

if __name__ == '__main__':
    list1 = [] #初始化一个列表,用于存放各个子线程   理解:“比如一个人买票的过程为一个子线程,那么n个人买票就是n个子线程,将这n个子线程存于列表中”
    for i in range(10): #10个人买票
        t = Thread(target=buy_ticket) #实例化子线程  (其实就是将买电影票过程实例化)
        list1.append(t) #将每一个人买票这个实例化对象存入列表中
        t.start() #启动线程
    for i in list1:
        t.join() #等待线程结束

假如没有互斥锁,即:

ticket_num = 100 #初始化100张电影票
#mutex = Lock() #实例化一个互斥锁

#规定子线程的执行方式(此处模拟买电影票的过程)
def buy_ticket():
    global ticket_num #声明ticket_num为全局变量
    #mutex.acquire() #锁定
    temp = ticket_num
    time.sleep(0.2)
    ticket_num = temp - 1
    print("购票成功,剩余%s张电影票。"%(ticket_num))
    #mutex.release() #释放锁

那么全局变量ticket_num 在初始化时(ticket_num =100时),就可以被存入列表List1中的10个所有子线程同时使用,那么打印的剩下的电影票数就都是“剩下99张”,明明出了10张,却剩下99,显然不对,这其实就是犯了10个人同时上1个卫生间的错误.....

再来理解一下互斥锁:

通过mutex.acquire()锁定,即子线程1在调用全局变量ticket_num时,可以有序进行。

互斥锁优点:

线程间的全局变量可以实现通信,互斥锁则解决了可能存在的“通信混乱”的问题。 

2.2 线程间的通信(通过队列)

Multiprocessing.Queue模块可以实现进程间的通信,同样的 线程之间也可以通过Queue实现线程间的通信,不同点是我们需要导入queue标准库下的Queue队列 使用的方法和Multiprocessing.Queue是相同的。

这个线程中使用的Queue,使用的是一个生产消费模式。

生产者:产生数据的模块

消费者:处理数据的模块

缓冲区:生产者和消费者之间的缓冲区是仓库,生产者负责向仓库添加商品,而消费者负责从仓库中取出商品,即:

 

 通过例子演示一下:

from queue import Queue #导入线程队列模块
import random,threading,time

#定义一个继承线程threading.Thread的子类,其实例化对象就是一个子线程(该线程即模拟,抽象生产者类的生产过程)
class Producer(threading.Thread):
    #重写threading.Thread的构造方法(应对子线程(生产过程)中的增加的静态属性,比如名字,数据等)
    def __init__(self,name,queue):
        threading.Thread.__init__(self,name=name)  #调用父类构造方法
        self.data = queue  # 将queue传递给参数self.data

    def run(self): #重写方法 (应对子线程(生产过程)中增加的动态属性,比如生产产品的过程【如下将其模拟成将产品加入队列】)
        for i in range(5):#生产5件产品
            print("生产者%s将产品%d加入到队列中!"%(self.getName(),i))  #self.getName() 表示获取生产者的名字
            self.data.put(i) #将数据(生产的产品)写进队列
            time.sleep(random.random()) #休眠随机时间
        print("生产者%s完成任务!"%(self.getName()))

# 定义一个继承线程threading.Thread的子类,其实例化对象就是一个子线程(该线程即模拟,抽象消费者类的消费过程)
class Custemer(threading.Thread):
    # 重写threading.Thread的构造方法(应对子线程(消费过程)中的增加的静态属性,比如名字,数据等)
    def __init__(self, name, queue):
        threading.Thread.__init__(self, name=name)  # 调用父类构造方法
        self.data = queue  # 将queue传递给参数self.data

    def run(self):  # 重写方法 (应对子线程(生产过程)中增加的动态属性,比如生产产品的过程【如下将其模拟成将产品加入队列】)
        for i in range(5):
            val = self.data.get() #从队列中取出数据(模拟消费过程)
            print("消费者%s将产品%d从队列中取出!" % (self.getName(), val))
            time.sleep(random.random())  # 休眠随机时间
        print("消费者%s完成消费!" % (self.getName()))

if __name__ == '__main__':
    print("---主线程启动---")
    queue = Queue() #实例化线程队列
    producer = Producer("香菱",queue)  # 实例化子进程 //继承子类带参数的情况,注意哦。xxx(xxx,ooo)
    consumer = Custemer("锅巴",queue)
    producer.start() #开始进程
    consumer.start()
    producer.join()
    consumer.join()
    print("---主线程结束---")


 总结:

(1)一个线程child_thread相对于python环境父类threading.Thread有其特殊的静态属性和动态属性,则可以通过定义一个继承threading.Thread的子类来实例化该线程child_thread。在继承的过程中,注意对child_thread的特殊静态属性和动态属性的安排,即方法重写:

class Producer(threading.Thread):
    #重写threading.Thread的构造方法(应对子线程(生产过程)中的增加的静态属性,比如名字,数据等)
    def __init__(self,name,queue):
        threading.Thread.__init__(self,name=name)  #调用父类构造方法
        self.data = queue  # 将queue传递给参数self.data

注意其参数传递方式。

(2) 对于如上:

self.data = queue  # 将queue传递给参数self.data

该代码将主程序中定义的队列queue赋值给了self.data,那么由队列存入和取出数据的操作就有如下代码:

 self.data.put(i) #将数据(生产的产品)写进队列
 val = self.data.get() #从队列中取出数据(模拟消费过程)

(3)由:

    def __init__(self,name,queue):
        threading.Thread.__init__(self,name=name)  #调用父类构造方法
        self.data = queue  # 将queue传递给参数self.data

则通过self.getName()可以读取name参数对应的值 。

3. 线程与进程的区别

(1)进程是系统进行资源分配和调度的一个独立单位,线程是进程的一个实体,是cpu调度和分派的基本单位。

比如QQ,对于任务管理器而言,运行的QQ就是一个进程;

而QQ中的收发信息,播放音乐,查看网页,下载文件等功能就是一个个线程。 

(2)进程之间是相互独立的,多进程中同一个变量,各自有一份备份存在每个进程中,但互不影响;而同一个进程的多个线程是内存共享的,所有变量都由所有线程共享。

(3)由于进程间是相互独立的,因此一个进程的崩溃不会影响其他进程;而线程是包含在进程之内的,线程的崩溃就会引发进程的崩溃,继而导致同一进程内的其他线程也崩溃。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Top Secret

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值