Python多任务——线程(1)

此Python多任务系列的笔记均来源于b站Python高级编程的视频资料


目录

一、线程的简单操作

查看线程数量

线程执行代码的封装

线程的执行顺序

总结

二、多线程-共享全局变量

传递参数

多线程-共享全局变量问题

三、同步、互斥锁

互斥锁


多任务:操作系统可以同时运行多个任务

  • 并发:指的是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执行(实际上总有一些任务不在执行,因为切换任务的速度相当快,所以看上去是一起执行的一样)
  • 并行:指的是任务数小于等于cpu核数核数,即任务真的是一起执行的

一、线程的简单操作

Python的thread模块是比较比较底层的模块,python的threading模块是对thread做的一些封装,更好的被使用

import time
import threading

def saySorry():
    print("dear i am sorry !")
    time.sleep(1)

def sayHappy():
    print("i am happy !")
    time.sleep(1)

for i in range (5):
    t1 = threading.Thread(target=saySorry)
    t2 = threading.Thread(target=sayHappy)
    t1.start()
    t2.start()
time2 = time.time()

#输出
dear i am sorry !
i am happy !
dear i am sorry !
i am happy !
dear i am sorry !
i am happy !
dear i am sorry !
i am happy !
dear i am sorry !
i am happy !

查看线程数量

import time
import threading


def saySorry():
    for i in range(3):
        print("dear i am sorry ---1")
        time.sleep(1)

def sayHappy():
    for i in range(3):
        print("i am happy     ---2")
        time.sleep(1)


if __name__ == "__main__":
    t1 = threading.Thread(target=saySorry)
    t2 = threading.Thread(target=sayHappy)
    t1.start()
    t2.start()
    print("-----------")
    while True:
        length = len(threading.enumerate())
        print("当前的线程数是:",length)
        if length <= 1:
            break

        time.sleep(0.5)

#输出
dear i am sorry ---1
i am happy     ---2
-----------
当前的线程数是: 3
当前的线程数是: 3
当前的线程数是: 3
dear i am sorry ---1
i am happy     ---2
当前的线程数是: 3
当前的线程数是: 3
i am happy     ---2
dear i am sorry ---1
当前的线程数是: 3
当前的线程数是: 2
当前的线程数是: 1

线程执行代码的封装

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

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

import time
import threading

class MyThread(threading.Thread):
    def run(self):
        for i in range(3):
            time.sleep(1)
            msg = "I am " + self.name + "//" +str(i)
            print(msg)

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

#输出
I am Thread-1//0
I am Thread-1//1
I am Thread-1//2

线程的执行顺序

多线程程序的执行顺序是不确定的。当执行到sleep语句时,线程将被阻塞(Blocked),到sleep结束后,线程就进入了就绪状态(Runnable),等待调度。而线程调度将自行选择一个线程执行。

下面的代码只能保证每一个线程都运行完整个run函数,但是线程的启动顺序,run函数中每次循环的执行顺序都不一样

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)
            print(msg)
def test():
    for i in range(5):
        t = MyThread()
        t.start()
if __name__ == '__main__':
    test()

#输出
I'm Thread-1 // 0
I'm Thread-2 // 0
I'm Thread-4 // 0
I'm Thread-3 // 0
I'm Thread-5 // 0
I'm Thread-1 // 1
I'm Thread-2 // 1
I'm Thread-4 // 1
I'm Thread-3 // 1
I'm Thread-5 // 1
I'm Thread-3 // 2
I'm Thread-4 // 2
I'm Thread-1 // 2
I'm Thread-2 // 2
I'm Thread-5 // 2

总结

  1. 每个线程默认有一个名字,尽管上面的例子中没有指定线程对象的name,但是python会自动为线程指定一个名字。
  2. 当线程的run()方法结束时该线程完成。
  3. 无法控制线程调度程序,但可以通过别的方式来影响线程调度的方式。

二、多线程-共享全局变量

但是要是前一个线程所用的时间比较长,那么可能后一个函数会提前运行...

缺点就是改变全局变量坑会造成多线程之间的混乱...

import time
from threading import Thread

g_num = 100

def work1():
    global g_num
    for i in range(5):
        g_num = g_num + 1
    print("----in work 1,g_num is %d" % g_num)

def work2():
    global g_num
    print("----in work 2,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()

#输出
在创建线程之前 g_num is 100
----in work 1,g_num is 105
----in work 2,g_num is 105

传递参数

from threading import Thread
import time

def work1(nums,a):
    nums.append(a)
    print("----in work1---",nums)


def work2(nums):
    #延时一会,保证t1线程中的事情做完
    time.sleep(1)
    print("----in work2---",nums)

g_nums = [11,22,33]

t1 = Thread(target=work1, args=(g_nums,20))
t1.start()

t2 = Thread(target=work2, args=(g_nums,))
t2.start()

#输出
----in work1--- [11, 22, 33, 20]
----in work2--- [11, 22, 33, 20]

多线程-共享全局变量问题

如果多个线程同时对一个局部变量进行操作,会出现资源竞争问题,从而导致数据会不准确

假设两个线程t1和t2都要对全局变量g_num(默认是0)进行加1运算,t1和t2都各对g_num加10次,g_num的最终的结果应该为20。

但是由于是多线程同时操作,有可能出现下面情况:

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

import threading
import time

g_nums = 0

def work1(nums):
    global g_nums
    for i in range (nums):
        g_nums = g_nums + 1
    print("----in work1---",g_nums)


def work2(nums):
    global g_nums
    for i in range (nums):
        g_nums = g_nums + 1
    print("----in work2---",g_nums)


t1 = threading.Thread(target=work1, args=(1000000,))
t1.start()

t2 = threading.Thread(target=work2, args=(1000000,))
t2.start()

while len(threading.enumerate()) != 1:
    time.sleep(1)

print('所有进程结束')

#输出
----in work1--- 1078248
----in work2--- 1522266
所有进程结束

三、同步、互斥锁

同步就是协同步调,按预定的先后顺序进行运行,有先后顺序

那如何来实现,同时来解决上面那个错误,可以利用线程同步来解决...上锁!

互斥锁

互斥锁为资源引入一个状态:锁定/非锁定

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

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

# 创建锁
mutex = threading.Lock()

# 锁定
mutex.acquire()

# 释放
mutex.release()
import threading
import time

g_nums = 0
mutex = threading.Lock()

def work1(nums):
    global g_nums
    for i in range (nums):
        mutex.acquire()
        g_nums = g_nums + 1
        mutex.release()
    print("----in work1---",g_nums)


def work2(nums):
    global g_nums
    for i in range (nums):
        mutex.acquire()
        g_nums = g_nums + 1
        mutex.release()
    print("----in work2---",g_nums)


t1 = threading.Thread(target=work1, args=(1000000,))
t1.start()

t2 = threading.Thread(target=work2, args=(1000000,))
t2.start()

while len(threading.enumerate()) != 1:
    time.sleep(1)

print('所有进程结束')

#输出
----in work1--- 1955581
----in work2--- 2000000
所有进程结束

我们可以试着这样理解,从1955581到2000000的计算都是第二个线程完成的

当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。

每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。

线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值