Python 多任务-线程

有关线程的知识点:

目录

- 多任务概念

- 线程概念

- 并发

- 并行

- 创建第一个多线程文件

- 线程执行代码的封装

- 多线程-共享全局变量

- 同步

- 互斥锁(死锁)

- 线程和进程的区别



我们先说多任务的概念:

什么叫“多任务”呢?简单地说,就是操作系统可以同时运行多个任务。打个比方:你一边吃饭,一边看电视,一边在用手机回消息,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。
线程进程 都可以实现多任务


线程的概念:


并发:

  • 指的是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执行(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已

并行:

  • 指的是任务数小于等于cpu核数,即任务真的是一起执行的

创建第一个多线程文件

  • 使用 threading模块 创建线程 , 引用 time模块 让我们更方便观察
  • 创建Thread对象 把任务添加进去
  • 开启线程执行任务
import threading
import time

# 创建一个打饭的函数
def buyfood(num):
	# 每个数字代表一个员工
    print("员工%d打饭" % num)
    # 等待两秒(假设每个人打饭要花两秒钟)
    time.sleep(2)


if __name__ == "__main__":
	# 1创建五个人序号是0~5
    for i in range(5):
        #2创建Thread对象 把任务添加进去
        t = threading.Thread(target=buyfood, args=(i,))
        #3.开启线程执行任务
        t.start()  # 启动线程,即让线程开始执行

Thread对象的参数1:把函数名字传进去. 参数2:传入参数1那个函数所需的参数(元组格式).
现在运行一下,可以明显看出,五个人是一瞬间同时完成,然后等待了两秒程序才结束了
注意: 当调用start()时,才会真正的创建线程,并且开始执行


线程执行代码的封装:

通过使用threading模块Thread类能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,直接定义一个新的子类(class) ,只要继承threading.Thread就可以了,然后重写run方法
步骤

  1. 导包threading

  2. 写一个类继承threading.Thread

  3. 重写run方法

  4. 实例化创建的类得到对象

  5. 调用对象的start开启任务

代码:

import threading
import time

class MyThread(threading.Thread):
    def run(self):
        for i in range(3):
            time.sleep(1)
            msg = "I'm "+self.name+' @ '+str(i) #name属性中保存的是当前线程的名字
            print(msg)


if __name__ == '__main__':
    t = MyThread()
    t.start()

说明:python的threading.Thread类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。而创建自己的线程实例后,通过Thread类的start方法,可以启动该线程,交给python虚拟机进行调度,当该线程获得执行的机会时,就会调用run方法执行线程。


多线程-共享全局变量

from threading import Thread
import time

# 定义变量
g_num = 100

def work1():
    # 声明global 使用全局变量
    global g_num
    for i in range(3):
        g_num += 1

    print("----in work1, g_num is %d---"%g_num)


def work2():
    global g_num
    print("----in work2, g_num is %d---"%g_num)


print("---线程创建之前g_num is %d---"%g_num)

t1 = Thread(target=work1)
t1.start()

#延时一会,保证t1线程中的事情做完
time.sleep(1)

t2 = Thread(target=work2)
t2.start()

结果:
运行结果图片
说明:

  • 在一个进程内的所有线程共享全局变量,很方便在多个线程间共享数据
  • 缺点就是,线程是对全局变量随意遂改可能造成多线程之间对全局变量的混乱(即线程非安全)
如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确

1 在g_num=0时,t1取得g_num=0。此时系统把t1调度为”sleeping”状态,把t2转换为”running”状态,t2也获得g_num=0
2 然后t2对得到的值进行加1并赋给g_num,使得g_num=1
3 然后系统又把t2调度为”sleeping”,把t1转为”running”。线程t1又把它之前得到的0加1后赋值给g_num。
4. 这样导致虽然t1和t2都对g_num加1,但结果仍然是g_num=1


同步

同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。(不是一起执行)
解决线程同时修改全局变量的方式可以通过线程同步
思路:

  1. 系统调用t1,然后获取到g_num的值为0,此时上一把锁,即不允许其他线程操作g_num

  2. t1对g_num的值进行+1

  3. t1解锁,此时g_num的值为1,其他的线程就可以使用g_num了,而且是g_num的值不是0而是1

  4. 同理其他线程在对g_num进行修改时,都要先上锁,处理完后再解锁,在上锁的整个过程中不允许其他线程访问,就保证了数据的正确性


互斥锁/死锁

线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁

: 某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

threading模块中早就定义了Lock类,可以方便的处理锁定:

# 创建锁
mutex = threading.Lock()

# 锁定
mutex.acquire()

# 释放
mutex.release()

注意:
如果这个锁之前是没有上锁的,那么acquire不会堵塞 .
如果在调用acquire对这个锁上锁之前 它已经被 其他线程上了锁,那么此时acquire会堵塞,直到这个锁被解锁为止

锁的好处:

确保了某段关键代码只能由一个线程从头到尾完整地执行

锁的坏处:

阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁
死锁 :

不好解释,还是代码好理解:

import threading
import time

class MyThread1(threading.Thread):
    def run(self):
        # 对mutexA上锁
        mutexA.acquire()

        # mutexA上锁后,延时1秒,等待另外那个线程 把mutexB上锁
        print(self.name+'----do1---up----')
        time.sleep(1)

        # 此时会堵塞,因为这个mutexB已经被另外的线程抢先上锁了
        mutexB.acquire()
        print(self.name+'----do1---down----')
        mutexB.release()

        # 对mutexA解锁
        mutexA.release()

class MyThread2(threading.Thread):
    def run(self):
        # 对mutexB上锁
        mutexB.acquire()

        # mutexB上锁后,延时1秒,等待另外那个线程 把mutexA上锁
        print(self.name+'----do2---up----')
        time.sleep(1)

        # 此时会堵塞,因为这个mutexA已经被另外的线程抢先上锁了
        mutexA.acquire()
        print(self.name+'----do2---down----')
        mutexA.release()

        # 对mutexB解锁
        mutexB.release()

mutexA = threading.Lock()
mutexB = threading.Lock()

if __name__ == '__main__':
    t1 = MyThread1()
    t2 = MyThread2()
    t1.start()
    t2.start()

线程vs进程

线程和进程肯定有区别 我在进程那篇文章里详细介绍了他们的区别 : Python 多任务-进程

GIL 问题 :在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势 .这个太底层有兴趣可以自己查一查


最后介绍一个利用多线程完成的小案例: UDP聊天室

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值