python 线程(1)-- 常用方法与属性,锁,同步

一.库介绍

线程管理使用threading模块

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全局超时时间。

二.创建线程

2.1 Thread类常用属性和方法如下。

Thread类属性:

  name:线程名

  ident:线程的标识符

  daemon:布尔值,表示这个线程是否是守护线程

Thread类方法:

  __init__(group=None,target=None,name=None,args=(),kwargs={},verbose=None,daemon=None):实例化一个线程对象,需要一个可调用的target对象,以及参数args或者kwargs。还可以传递name和group参数。daemon的值将会设定thread.daemon的属性

  start():开始执行该线程

  run():定义线程的方法。(通常开发者应该在子类中重写)

  join(timeout=None):直至启动的线程终止之前一直挂起;除非给出了timeout(单位秒),否则一直被阻塞

  isAlive:布尔值,表示这个线程是否还存活(驼峰式命名,python2.6版本开始已被取代)

  isDaemon():布尔值,表示是否是守护线程

  setDaemon(布尔值):在线程start()之前调用,把线程的守护标识设定为指定的布尔值

2.2 目标函数作为参数的方式

import os

import time

import threading

def fun(n):

    print('子线程开始运行……')

    time.sleep(1)

    my_thread_name = threading.current_thread().name#获取当前线程名称

    my_thread_id = threading.current_thread().ident#获取当前线程id

    print('当前线程为:{},线程id为:{},所在进程为:{},您输入的参数为:{}'.format(my_thread_name ,my_thread_id , os.getpid(),n))

    print('子线程运行结束……')

 

t = threading.Thread(target=fun , name='线程1',args=('参数1',))

t.start()

time.sleep(2)

main_thread_name = threading.current_thread().name#获取当前线程名称

main_thread_id = threading.current_thread().ident#获取当前线程id

print('主线程为:{},线程id为:{},所在进程为:{}'.format(main_thread_name ,main_thread_id , os.getpid()))

2.3 继承threading.thread的方式

import os

import time

import threading

class MyThread(threading.Thread):

    def __init__(self , n , name=None):

        super().__init__()

        self.name = name

        self.n = n

    def run(self):

        print('子线程开始运行……')

        time.sleep(1)

        my_thread_name = threading.current_thread().name#获取当前线程名称

        my_thread_id = threading.current_thread().ident#获取当前线程id

        print('当前线程为:{},线程id为:{},所在进程为:{},您输入的参数为:{}'.format(my_thread_name ,my_thread_id , os.getpid(),self.n))

        print('子线程运行结束……')

t = MyThread(name='线程1', n=1)

t.start()

time.sleep(2)

main_thread_name = threading.current_thread().name#获取当前线程名称

main_thread_id = threading.current_thread().ident#获取当前线程id

print('主线程为:{},线程id为:{},所在进程为:{}'.format(main_thread_name ,main_thread_id , os.getpid()))

三.常用方法与属性

3.1 守护线程:deamon

Thread类有一个名为deamon的属性,标志该线程是否为守护线程,默认值为False,当为设为True是表示设置为守护线程。是否是守护线程有什么区别呢?

当deamon值为True,即设为守护线程后,只要主线程结束了,无论子线程代码是否结束,都得跟着结束,这就是守护线程的特征。另外,修改deamon的值必须在线程start()方法调用之前,否则会报错。

可查看另一篇文章:python 线程(4)-- 守护线程与用户线程

3.2 join方法

join()方法的作用是在调用join()方法处,让所在线程(主线程)同步的等待被join的线程,等到join的线程结束后才执行当前所在线程

import threading
import time

def run():

    time.sleep(2)
    print('当前线程的名字是: ', threading.current_thread().name)
    time.sleep(2)


if __name__ == '__main__':

    start_time = time.time()

    print('这是主线程:', threading.current_thread().name)
    thread_list = []
    for i in range(5):
        t = threading.Thread(target=run)
        thread_list.append(t)

    for t in thread_list:
        #t.setDaemon(True)
        t.start()

    for t in thread_list:
        t.join()

    print('主线程结束了!' , threading.current_thread().name)
    print('一共用时:', time.time()-start_time)


》》》》
这是主线程: MainThread
当前线程的名字是:  Thread-4
当前线程的名字是:  Thread-3
当前线程的名字是:  Thread-2
当前线程的名字是:  Thread-1
当前线程的名字是:  Thread-5
主线程结束了! MainThread
一共用时: 4.055499315261841

以上可看到:五个线程依次join,主线程(所在线程)等待他们结束才往下执行,而其他线程(同样调用join的线程)没有等待前面join的线程在正常执行。

四.线程同步

4.1 互斥锁 Lock

线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资源设置一个状态:锁定和非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

import time

import threading

def fun(lock):

    re = lock.acquire(timeout = 3) #默认阻塞线程,直到超时
    if not re:
        print('get lock failed')
        return

    global num

    temp = num

    time.sleep(0.2)

    temp -= 1

    num = temp

    lock.release()

print('主线程开始运行……')

t_lst = []

num =10 # 全局变量

lock = threading.Lock()

for i in range(10):

    t = threading.Thread(target=fun , args=(lock,))

    t_lst.append(t)

    t.start()

[t.join() for t in t_lst]

print('num最后的值为:{}'.format(num))

print('主线程结束运行……')

死锁

使用Lock的时候必须注意是否会陷入死锁,所谓死锁是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。关于死锁一个著名的模型是“科学家吃面”模型:

import time

from threading import Thread

from threading import Lock

def eatNoodles_1(noodle_lock, fork_lock, scientist):

    noodle_lock.acquire()

    print('{} 拿到了面'.format(scientist))

    fork_lock.acquire()

    print('{} 拿到了叉子'.format(scientist))

    time.sleep(1)

    print('{} 吃到了面'.format(scientist))

    fork_lock.release()

    noodle_lock.release()

    print('{} 放下了面'.format(scientist))

    print('{} 放下了叉子'.format(scientist))

def eatNoodles_2(noodle_lock, fork_lock, scientist):

    fork_lock.acquire()

    print('{} 拿到了叉子'.format(scientist))

    noodle_lock.acquire()

    print('{} 拿到了面'.format(scientist))

    print('{} 吃到了面'.format(scientist))

    noodle_lock.release()

    print('{} 放下了面'.format(scientist))

    fork_lock.release()

    print('{} 放下了叉子'.format(scientist))

 

scientist_list1 = ['霍金','居里夫人']

scientist_list2 = ['爱因斯坦','富兰克林']

noodle_lock  = Lock()

fork_lock = Lock()

for i in scientist_list1:

    t = Thread(target=eatNoodles_1, args=(noodle_lock, fork_lock, i))

    t.start()

for i in scientist_list2:

    t = Thread(target=eatNoodles_2, args=(noodle_lock, fork_lock, i))

t.start()

》》
霍金 拿到了面
霍金 拿到了叉子
霍金 吃到了面
富兰克林 拿到了叉子居里夫人 拿到了面

霍金 放下了面
霍金 放下了叉子

#富兰克林和居里夫人一个拿了面等叉子,一个拿了叉子等面,进入死锁

4.2 递归锁:RLock

所谓的递归锁也被称为“锁中锁”,指一个线程可以多次申请同一把锁,但是不会造成死锁,这就可以用来解决上面的死锁问题。

import time

from threading import Thread

from threading import RLock

def eatNoodles_1(noodle_lock, fork_lock, scientist):

    noodle_lock.acquire()

    print('{} 拿到了面'.format(scientist))

    fork_lock.acquire()

    print('{} 拿到了叉子'.format(scientist))

    time.sleep(1)

    print('{} 吃到了面'.format(scientist))

    fork_lock.release()

    noodle_lock.release()

    print('{} 放下了面'.format(scientist))

    print('{} 放下了叉子'.format(scientist))

def eatNoodles_2(noodle_lock, fork_lock, scientist):

    fork_lock.acquire()

    print('{} 拿到了叉子'.format(scientist))

    noodle_lock.acquire()

    print('{} 拿到了面'.format(scientist))

    print('{} 吃到了面'.format(scientist))

    noodle_lock.release()

    print('{} 放下了面'.format(scientist))

    fork_lock.release()

    print('{} 放下了叉子'.format(scientist))

 

scientist_list1 = ['霍金','居里夫人']

scientist_list2 = ['爱因斯坦','富兰克林']

noodle_lock=fork_lock = RLock()

for i in scientist_list1:

    t = Thread(target=eatNoodles_1, args=(noodle_lock, fork_lock, i))

    t.start()

for i in scientist_list2:

    t = Thread(target=eatNoodles_2, args=(noodle_lock, fork_lock, i))

t.start()

上面代码可以正常运行到所有科学家吃完面条。

  RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。

上面的例子如果使用RLock代替Lock,则不会发生死锁,二者的区别是:递归锁可以连续acquire多次,而互斥锁只能acquire一次

4.3 Condition

Condition可以认为是一把比Lock和RLOK更加高级的锁,其在内部维护一个琐对象(默认是RLock),可以在创建Condigtion对象的时候把琐对象作为参数传入。Condition也提供了acquire, release方法,其含义与琐的acquire, release方法一致,其实它只是简单的调用内部琐对象的对应的方法而已。Condition内部常用方法如下:

  1)acquire(): 上线程锁

  2)release(): 释放锁

  3)wait(timeout): 线程挂起,直到收到一个notify通知或者超时(可选的,浮点数,单位是秒s)才会被唤醒继续运行。wait()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。

  4)notify(n=1): 通知其他线程,那些挂起的线程接到这个通知之后会开始运行,默认是通知一个正等待该condition的线程,最多则唤醒n个等待的线程。notify()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。notify()不会主动释放Lock。

  5)notifyAll(): 如果wait状态线程比较多,notifyAll的作用就是通知所有线程

  需要注意的是,notify()方法、notifyAll()方法只有在占用琐(acquire)之后才能调用,否则将会产生RuntimeError异常。

  用Condition来实现生产者消费者模型:

import threading

import time

 

# 生产者

def produce(con):

    # 锁定线程

    global num

    con.acquire()

    print("工厂开始生产……")

    while True:

        num += 1

        print("已生产商品数量:{}".format(num))

        time.sleep(1)

        if num >= 5:

            print("商品数量达到5件,仓库饱满,停止生产……")

            con.notify()  # 唤醒消费者

            con.wait()# 生产者自身陷入沉睡

    # 释放锁

    con.release()
# 消费者

def consumer(con):

    con.acquire()

    global num

    print("消费者开始消费……")

    while True:

        num -= 1

        print("剩余商品数量:{}".format(num))

        time.sleep(2)

        if num <= 0:

            print("库存为0,通知工厂开始生产……")

            con.notify()  # 唤醒生产者线程

            con.wait()    # 消费者自身陷入沉睡

    con.release()

con = threading.Condition()

num = 0

p = threading.Thread(target=produce , args=(con ,))

c = threading.Thread(target=consumer , args=(con ,))

p.start()

c.start()

》》
工厂开始生产……
已生产商品数量:1
已生产商品数量:2
已生产商品数量:3
已生产商品数量:4
已生产商品数量:5
商品数量达到5件,仓库饱满,停止生产……
消费者开始消费……
剩余商品数量:4
剩余商品数量:3
剩余商品数量:2
剩余商品数量:1
剩余商品数量:0
库存为0,通知工厂开始生产……
已生产商品数量:1
已生产商品数量:2
已生产商品数量:3
已生产商品数量:4
已生产商品数量:5
商品数量达到5件,仓库饱满,停止生产……
剩余商品数量:4
剩余商品数量:3

4.4 信号量:Semaphore

锁同时只允许一个线程更改数据,而信号量是同时允许一定数量的进程更改数据 。继续用上篇博文中用的的吃饭例子,加入有一下应用场景:有10个人吃饭,但只有一张餐桌,只允许做3个人,没上桌的人不允许吃饭,已上桌吃完饭离座之后,下面的人才能抢占桌子继续吃饭,如果不用信号量,肯定是10人一窝蜂一起吃饭:

from threading import Thread

import time

import random

from threading import Semaphore

def fun(i , sem):

    sem.acquire()

    print('{}号顾客上座,开始吃饭'.format(i))

    time.sleep(random.random())

    print('{}号顾客吃完饭了,离座'.format(i))

    sem.release()

if __name__=='__main__':

    sem = Semaphore(3)

    for i in range(6):

        p = Thread(target=fun, args=(i,sem))

        p.start()

》》
0号顾客上座,开始吃饭
1号顾客上座,开始吃饭
2号顾客上座,开始吃饭
1号顾客吃完饭了,离座
3号顾客上座,开始吃饭
2号顾客吃完饭了,离座
4号顾客上座,开始吃饭
3号顾客吃完饭了,离座
5号顾客上座,开始吃饭
4号顾客吃完饭了,离座
0号顾客吃完饭了,离座
5号顾客吃完饭了,离座

五.定时器Timer

如果想要实现每隔一段时间就调用一个函数的话,就要在Timer调用的函数中,再次设置Timer。Timer是Thread类的一个子类。

  如果是多长时间后只执行一次:

import threading

import time

def sayTime(name):

    print('你好,{}为您报时,现在时间是:{}'.format(name , time.ctime()))


if __name__ == "__main__":

     timer = threading.Timer(2.0, sayTime, ["Jane"])

     timer.start()

输出结果:

你好,Jane为您报时,现在时间是:Thu Dec  6 15:03:41 2018

如果要每个多长时间执行一次:

import threading

import time

def sayTime(name):

    print('你好,{}为您报时,现在时间是:{}'.format(name , time.ctime()))

    global timer

    timer = threading.Timer(3.0, sayTime, [name])

    timer.start()

if __name__ == "__main__":

     timer = threading.Timer(2.0, sayTime, ["Jane"])

     timer.start()

参考:

Python并发编程系列之多线程 - 奥辰 - 博客园

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python中,可以使用线同步(Thread Lock)来保证多个线程之间的互斥访问共享资源。线同步可以防止多个线程同时修改共享变量,从而避免数据不一致的问题。 Python提供了threading模块来支持多线程编程,并且提供了Lock类作为线同步的实现。下面是一个简单的示例代码: ```python import threading # 创建一个对象 lock = threading.Lock() # 共享变量 counter = 0 def increment(): global counter for _ in range(100000): # 获取 lock.acquire() try: counter += 1 finally: # 释放 lock.release() # 创建多个线程并启动 threads = [] for _ in range(10): t = threading.Thread(target=increment) threads.append(t) t.start() # 等待所有线程执行完毕 for t in threads: t.join() # 打印结果 print("Counter:", counter) ``` 在上述示例中,我们使用了一个全局变量`counter`作为共享资源,并创建了一个对象`lock`。在`increment`函数中,我们首先通过`lock.acquire()`获取,然后在临界区内对`counter`进行操作,最后通过`lock.release()`释放。这样,每次只有一个线程能够获取到,从而保证了共享资源的安全访问。 需要注意的是,获取后一定要在`finally`块中释放,以确保无论是否发生异常,都能够正确地释放线同步是一种简单有效的线同步机制,但在Python中还有其他更高级的同步原语,如条件变量、信号量等,可以根据具体的需求选择合适的同步机制。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值