python多任务——多线程(threading)及线程同步

一、多任务

多任务处理是指用户可以在同一时间内运行多个应用程序,每个应用程序被称作一个任务.Linux、windows就是支持多任务的操作系统,比起单任务系统它的功能增强了许多。

当多任务操作系统使用某种任务调度策略允许两个或更多进程并发共享一个处理器时,事实上处理器在某一时刻只会给一件任务提供服务。因为任务调度机制保证不同任务之间的切换速度十分迅速,因此给人多个任务同时运行的错觉。多任务系统中有3个功能单位:任务、进程和线程。

二、线程

python的thread模块是比较底层的模块,python的threading模块是对thread做了一些包装的,可以更加方便的被使用。

threading用于提供线程相关的操作,线程是应用程序中工作的最小单元。python当前版本的多线程库没有实现优先级、线程组,线程也不能被停止、暂停、恢复、中断。

threading模块提供的类:  
  Thread, Lock, Rlock, Condition, [Bounded]Semaphore, Event, Timer, local。

threading 模块提供的常用方法: 
  threading.currentThread(): 返回当前的线程变量。 
  threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 
  threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

threading 模块提供的常量:

  threading.TIMEOUT_MAX 设置threading全局超时时间。

1、创建线程

Thread是线程类,有两种使用方法,直接传入要运行的方法或从Thread继承并覆盖run():

t1 = threading.Thread(target = 函数名)

(1)方法一:将要执行的方法作为参数传给Thread的构造方法

# coding:utf-8
import threading
import time
def action(arg):
    time.sleep(1)
    print 'the arg is:%s\r' %arg

for i in xrange(4):
    t =threading.Thread(target=action,args=(i,))
    t.start()

print 'main thread end!'

(2)方法二:从Thread继承,并重写run()

注:start方法内部自动调用了run方法

import threading
import time
class MyThread(threading.Thread):
    def __init__(self,arg):
        super(MyThread, self).__init__()#注意:一定要显式的调用父类的初始化函数。
        self.arg=arg
    def run(self):#定义每个线程要运行的函数
        time.sleep(1)
        print 'the arg is:%s\r' % self.arg
for i in xrange(4):
    t =MyThread(i)
    t.start() 

print 'main thread end!'

构造方法: 
Thread(group=None, target=None, name=None, args=(), kwargs={}) 

  •   group: 线程组,目前还没有实现,库引用中提示必须是None; 
  •   target: 要执行的方法; 
  •   name: 线程名; 
  •   args/kwargs: 要传入方法的参数。

(3)实例方法: 

isAlive(): 返回线程是否在运行。正在运行指启动后、终止前。 
get/setName(name): 获取/设置线程名。 

start():  线程准备就绪,等待CPU调度

is/setDaemon(bool): 获取/设置是后台线程(默认前台线程(False))。(在start之前设置)

  • 如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,主线程和后台线程均停止
  • 如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止

start(): 启动线程。 
join([timeout]): 阻塞当前上下文环境的线程,直到调用此方法的线程终止或到达指定的timeout(可选参数)。

2、enumerate函数

学习enumerate()函数,我们先弄清楚的是,enumerate()是什么,可以用来干嘛?

  • enumerate()是Python的内置函数
  • 用于可迭代\可遍历的数据对象组合为一个索引序列,同时列出数据和数据下标
  • 一般在for循环中使用

我们来看看enumerate()的使用用例:这里,我们只要一个for循环就得到全部的索引和对应的元素

 

当然,这里的索引是从0开始,我们也可以指定索引的开始值,这里我们将索引值指定从1开始。

 

再看看下面:

threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。

返回一个list类型,通过这个list类型我们就可以得知,目前线程数量的多少。

import threading
import time

def myTest():
    for i in range(5):
        print("Hanmh_ -> %d" % i)
        time.sleep(1)

def myTest2():
    for i in range(5):
        print("Hanmh2_ -> %d" % i)
        time.sleep(1)

def main():
    t1 = threading.Thread(target=myTest)
    t2 = threading.Thread(target=myTest2)

    t1.start()
    t2.start()

    while True:
        print(threading.enumerate())
        if len(threading.enumerate()) <= 1:
            break
        time.sleep(1)

if __name__ == '__main__':
    main()

三、多个线程共享全局变量

在一个函数中对一个全局变量进行修改时,进行说明:要看是否对全局变量的指向进行了修改,如果修改了指向即让全局变量指向了一个新的地方,那么必须使用global。如果仅仅是修改了指向的空间中的数据,此时不必用global

如果做了一个多线程的多任务的程序,子线程与子线程之间共享全局变量:

import threading
import time

"""线程共享全局变量"""
#定义一个全局变量
g_num = 100

def test1():
    global g_num
    g_num += 1
    print("-------test1 g_num = %d" % g_num)
    return None

def test2():
    print("-------test2 g_num = %d" % g_num)

def main():
    t1 = threading.Thread(target=test1)
    t2 = threading.Thread(target=test2)
    t1.start()
    time.sleep(1)
    t2.start()
    time.sleep(1)
    print("-------main Thread g_num = %d" % g_num)

if __name__ == '__main__':
    main()

1、args参数

t = threading.Thread(target = xxx,args = (元组))

  • args指定将来调用函数的时候,传递什么数据过去
import threading
import time

"""线程共享全局变量"""
def test1(temp):
    temp.append(33) #不修改全局变量指向,所以不用global
    print("-------test1 g_nums = %s" % str(temp))
    return None

def test2(temp):
    print("-------test2 g_nums = %s" % str(temp))

g_nums = [11, 22]

def main():
    t1 = threading.Thread(target=test1, args=(g_nums,))
    t2 = threading.Thread(target=test2, args=(g_nums,))
    t1.start()
    time.sleep(1)
    t2.start()
    time.sleep(1)
    print("-------main Thread g_nums = %s" % str(g_nums))

if __name__ == '__main__':
    main()

2、多线程同时对全局变量进行操作带来的负面影响(资源竞争)

g_num += 1:这步是出现争议的地方,因为计算机在执行这个操作时,会将其分成多步执行,多线程情况下,会出现资源分配问题,竞争资源的过程中,有可能会出现某个流程就没进行完,就切换至其他线程执行

import threading
import time

g_num = 0

"""线程共享全局变量"""
def test1(num):
    global g_num
    for i in range(num):
        g_num += 1
    print("-------in test1 g_num = %d" % g_num)

def test2(num):
    global g_num
    for i in range(num):
        g_num += 1
    print("-------in test2 g_num = %d" % g_num)

def main():
    t1 = threading.Thread(target=test1, args=(10000000,))
    t2 = threading.Thread(target=test2, args=(10000000,))
    t1.start()
    t2.start()
    time.sleep(5)
    print("-------main Thread g_num = %d" % g_num)

if __name__ == '__main__':
    main()

四、多线程资源竞争问题解决

解决办法:线程同步(协调同步,按预定的先后次序进行运行)

把需要执行的操作,做成原子操作。(要么不做,要做就一次性完成)

1、互斥锁

当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制。线程同步能够保障多个线程安全访问竞争资源,最简单的同步机制就是引入互斥锁。

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

  • 创建锁:mutex = threading.Lock()
  • 锁定锁:mutex.acquire()
  • 释放锁:mutex.release()

注意:

  • 如果这个锁之前是没有上锁的,那么acquire不会堵塞
  • 如果在调用acquire对这个锁上锁之前,它已经被其他线程上了锁,那么此时的acquire操作需要等待锁被解锁为止。
import threading
import time

g_num = 0

"""线程共享全局变量"""
def test1(num):
    global g_num
    #上锁,如果没有被上锁,就会上锁成功,否则阻塞在这里
    mutex.acquire()
    for i in range(num):
        g_num += 1
    #解锁
    mutex.release()
    print("-------in test1 g_num = %d" % g_num)

def test2(num):
    global g_num
    mutex.acquire()
    for i in range(num):
        g_num += 1
    mutex.release()
    print("-------in test2 g_num = %d" % g_num)

#创建一个互斥锁,默认是没有上锁的
mutex = threading.Lock()

def main():
    t1 = threading.Thread(target=test1, args=(10000000,))
    t2 = threading.Thread(target=test2, args=(10000000,))
    t1.start()
    t2.start()
    time.sleep(5)
    print("-------main Thread g_num = %d" % g_num)

if __name__ == '__main__':
    main()

(2)上锁范围在不影响结果的情况下,最好缩小范围

缩小范围,会减小其他线程的等待时间,避免忙等。

"""线程共享全局变量"""
def test1(num):
    global g_num
    for i in range(num):
        # 上锁,如果没有被上锁,就会上锁成功,否则阻塞在这里
        mutex.acquire()
        g_num += 1
        #解锁
        mutex.release()
    print("-------in test1 g_num = %d" % g_num)

def test2(num):
    global g_num
    for i in range(num):
        mutex.acquire()
        g_num += 1
        mutex.release()
    print("-------in test2 g_num = %d" % g_num)

2、死锁

在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。

import threading
import time

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

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

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

        #对mutexA解锁
        mutexA.release()


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

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

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

        # 对mutexA解锁
        mutexB.release()

#互斥锁
mutexA = threading.Lock()
mutexB = threading.Lock()

def main():
    t1 = MyThread1()
    t2 = MyThread2()
    t1.start()
    t2.start()
    return None

if __name__ == '__main__':
    main()

(1)避免死锁的方法

  • 程序设计时要尽量避免(银行家算法)
  • 添加超时时间等

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Star星屹程序设计

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

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

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

打赏作者

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

抵扣说明:

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

余额充值