Python学习017_多线程-共享全局变量问题

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

多线程可能遇到的问题

假设有两个线程t1和t2,都要对一个变量g_num进行运算(+1运算),两个线程t1和t2分别对g_num各加10次,g_num的最终结果?

import threading
import time

g_num = 0

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

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

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

t1 = threading.Thread(target=work1, args=(10,))
t2 = threading.Thread(target=work2, args=(10,))

t1.start()
t2.start()

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

print("2个线程对同一变量操作之后的最终结果: %d" %g_num)

输出结果:

---线程创建之前g_num: 0
---in work1,g_num is 10---
---in work2,g_num is 20---
2个线程对同一变量操作之后的最终结果: 20

在num=0时,t1取得num=0,此时系统把t调度为“sleeping”的状态,t2装换为“running”的状态,t2也获得num=0.然后t2对得到的值进行加1并赋值给num。num= 1,然后系统又将t2调度为“sleeping”的状态,把t1转换为“running”。线程t1又把它之前得到的0加1赋值给num。这种情况,明明两个线程都完成了一次+1工作,但结果还是num = 1.

如果我们将两个线程的参数调整为1000000,多次运行,结果不同。

说明多个线程同时对一个全局变量进行操作,会出现资源竞争问题,从而使数据结果不正确。

同步

同步,就是协同步调。按照预定的先后次序进行运行。好比交流,一个说完,另一个人再说。

进程和线程的同步,可以理解为进程或者线程A和B一块配合,A执行一定程度时需要依赖B的某个结果,于是停下来,让B运行,B开始运行,再将结果给A,A再继续操作。如此往复。直至程序结束。

计算错误的解决

通过线程同步进行解决

思路:

  • 系统调度t1,获取num=0,此时上一把锁,即不允许其他线程操作num
  • 对num的值加1
  • 解锁,此时num的值为1,其他的线程就可以使用num了。此时num = 1
  • 同理,其他线程在对num修改时,也要先上锁,处理完后再解锁。在上锁的过程中不允许其他线程访问。就保证了数据的正确性。

互斥锁

当多个线程几乎同时修改摸个共享数据时,需要进行同步控制

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

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

某个线程更改共享数据时,先将其锁定,此时,资源的状态为锁定,其他线程就不能对其修改;直到该线程释放资源,资源状态变为非锁定状态,其他线程才能再次锁定该资源。

互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性

在threading模块里,定义了Lock()类,可以方便的处理锁定。

mutex = threading.Lock() #创建锁
mutex.acquire([blocking]) #锁定
mutex.release() #释放

说明:

  • blocking
    • True,则当前线程堵塞,直到获取这个锁为止(如未指定,则默认为True)
    • False ,则线程不会被堵塞
import threading
import time

g_num = 0

def work1(num):
    global g_num
    for i in range(num):
        if mutex.acquire(True):
            g_num += 1
            mutex.release()
    print("---in work1,g_num is %d---" % g_num)

def work2(num):
    global g_num
    for i in range(num):
        if mutex.acquire(True):
            g_num += 1
            mutex.release()
    print("---in work2,g_num is %d---" % g_num)

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

mutex = threading.Lock()

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

t1.start()
time.sleep(1)
t2.start()

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

print("2个线程对同一变量操作之后的最终结果: %d" %g_num)

死锁

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

死锁一般很少发生,但一旦发生就会造成应用停止响应。

import threading
import time

printer_mutex = threading.Lock() # 打印机锁

paper_mutext = threading.Lock() # 纸张锁


class ResumeThread(threading.Thread):
    """编写个人简历任务的线程"""

    def run(self):
        print("ResumeThread:编写个人简历任务")
        # 使用打印机资源,先对打印机加锁
        printer_mutex.acquire()
        print("--ResumeThread:正在使用打印机资源--")
        time.sleep(1)  # 休眠1秒

        # 使用纸张耗材,先对纸张耗材加锁
        paper_mutext.acquire()
        print("--正在使用纸张资源--")
        time.sleep(1)
        paper_mutext.release()  # 释放纸张锁

        # 释放打印机锁
        printer_mutex.release()


class PaperListThread(threading.Thread):
    """盘点纸张耗材任务的线程"""

    def run(self):
        print("PaperListThread:盘点纸张耗材任务")
        # 使用纸张耗材,先对纸张耗材加锁
        paper_mutext.acquire()
        print("--PaperListThread:正在盘点纸张耗材--")
        time.sleep(1)  # 休眠1秒

        # 使用打印机资源,打印清单
        printer_mutex.acquire()
        print("--正在使用打印机资源--")
        time.sleep(1)
        printer_mutex.release()  # 释放打印机锁

        # 释放纸张耗材锁
        paper_mutext.release()


if __name__ == '__main__':
    t1 = ResumeThread()
    t2 = PaperListThread()

    t1.start()
    t2.start()

同步应用

让多个线程有序的执行

生产者消费者问题

也就是有限缓冲问题,是一个多线程同步的经典案例

描述了一个两个固定大小缓冲区的线程—即所谓的“生产者”和“消费者”—在实际运行时会发生的问题。

生产者的主要作用,生成一定量的数据放在缓冲区中,然后,重复此过程。

与此同时。消费者也在缓冲区消耗这些数据。

整个问题关键是,生产者不会再缓冲区满时加入数据,消费者也不会在缓冲区空时消耗数据。

解决办法
要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题,常用的方法有信号灯法等。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形

import threading
import time
from queue import Queue

class Producer(threading.Thread):
    def run(self):
        global queue
        count = 0
        while True:
            if queue.qsize() < 1000:
                for i in range(100):
                    count = count +1
                    msg = '生成产品'+str(count)
                    queue.put(msg)
                    print(msg)
            time.sleep(0.5)

class Consumer(threading.Thread):
    def run(self):
        global queue
        while True:
            if queue.qsize() > 100:
                for i in range(3):
                    msg = self.name + '消费了 '+queue.get()
                    print(msg)
            time.sleep(1)


if __name__ == '__main__':
    queue = Queue()

    for i in range(500):
        queue.put('初始产品'+str(i))
    for i in range(2):
        p = Producer()
        p.start()
    for i in range(5):
        c = Consumer()
        c.start()

1.队伍,先进先出

2.栈 ,后进先出

Python中queue(py3) (py2,Queue),模块提供了一个同步的、线程安全的队列类,包括先入先出(FIFO)队列Queue,和后入先出(LIFO)队列LifeQueue和优先级队列PriorityQueue。

这些队列实现了锁原语(原子操作,要么不做,要么做完),可以在线程中直接使用。

可以使用队列来实现线程间的同步。

FIFO队列实现生产者消费者问题。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值