【20天快速掌握Python】day17-线程

20 篇文章 0 订阅
19 篇文章 1 订阅

1.线程安全问题

1.1 线程访问全局变量

import threading
g_num = 0
def test(n):
    global g_num
    for x in range(n):
        g_num += x
        g_num -= x
    print(g_num)

if __name__ == '__main__':
    t1 = threading.Thread(target=test, args=(10,))
    t2 = threading.Thread(target=test, args=(10,))
    t1.start()
    t2.start()

在一个进程内的所有线程共享全局变量,很方便在多个线程间共享数据。缺点就是,线程是对全局变量随意遂改可能造成多线程之间对全局变量的混乱(即线程非安全)。

1.2 线程的安全问题

import threading
import time

ticket = 20


def sell_ticket():
    global ticket
    while True:
        if ticket > 0:
            time.sleep(0.5)
            ticket -= 1
            print('{}卖了一张票,还剩{}'.format(threading.current_thread().name, ticket))
        else:
            print('{}票卖完了'.format(threading.current_thread().name))
            break


for i in range(5):
    t = threading.Thread(target=sell_ticket, name='thread-{}'.format(i + 1))
    t.start()

2.线程锁

2.1 同步

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

2.2 互斥锁

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

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

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

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

注意:

  • 如果这个锁之前是没有上锁的,那么acquire不会堵塞

  • 如果在调用acquire对这个锁上锁之前 它已经被 其他线程上了锁,那么此时acquire会堵塞,直到这个锁被解锁为止。

  • 和文件操作一样,Lock也可以使用with语句快速的实现打开和关闭操作。

2.3 使用互斥锁解决卖票问题

import threading
import time

ticket = 20
lock = threading.Lock()


def sell_ticket():
    global ticket
    while True:
        lock.acquire()
        if ticket > 0:
            time.sleep(0.5)
            ticket -= 1
            lock.release()
            print('{}卖了一张票,还剩{}'.format(threading.current_thread().name, ticket))
        else:
            print('{}票卖完了'.format(threading.current_thread().name))
            lock.release()
            break


for i in range(5):
    t = threading.Thread(target=sell_ticket, name='thread-{}'.format(i + 1))
    t.start()

2.4 上锁过程:

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

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

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

总结

锁的好处:

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

锁的坏处:

  • 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。

  • 由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁。

3.线程间通信

3.1 线程间通过

线程之间有时需要通信,操作系统提供了很多机制来实现进程间的通信,其中我们使用最多的是队列Queue.

3.2 Queue的原理

Queue是一个先进先出(First In First Out)的队列,主进程中创建一个Queue对象,并作为参数传入子进程,两者之间通过put( )放入数据,通过get( )取出数据,执行了get( )函数之后队列中的数据会被同时删除,可以使用multiprocessing模块的Queue实现多进程之间的数据传递。

import threading
import time
from queue import Queue

def producer(queue):
    for i in range(100):
        print('{}存入了{}'.format(threading.current_thread().name, i))
        queue.put(i)
        time.sleep(0.1)
    return

def consumer(queue):
    for x in range(100):
        value = queue.get()
        print('{}取到了{}'.format(threading.current_thread().name, value))
        time.sleep(0.1)
        if not value:
            return

if __name__ == '__main__':
    queue = Queue()
    t1 = threading.Thread(target=producer, args=(queue,))
    t2 = threading.Thread(target=consumer, args=(queue,))
    t3 = threading.Thread(target=consumer, args=(queue,))
    t4 = threading.Thread(target=consumer, args=(queue,))
    t6 = threading.Thread(target=consumer, args=(queue,))
    t1.start()
    t2.start()
    t3.start()
    t4.start()
    t6.start()

4.多线程版聊天

import socket
import threading

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('0.0.0.0', 8080))


def send_msg():
    ip = input('请输入您要聊天的ip:')
    port = int(input('请输入对方的端口号:'))
    while True:
        msg = input('请输入聊天内容:')
        s.sendto(msg.encode('utf-8'), (ip, port))
        if msg == "bye":
            ip = input('请输入您要聊天的ip:')
            port = int(input('请输入对方的端口号:'))


def recv_msg():
    while True:
        content, addr = s.recvfrom(1024)
        print('接收到了{}主机{}端口的消息:{}'.format(addr[0], addr[1], content.decode('utf-8')),file=open('history.txt', 'a', encoding='utf-8'))


send_thread = threading.Thread(target=send_msg)
recv_thread = threading.Thread(target=recv_msg)

send_thread.start()
recv_thread.start()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值