多任务---线程

linux : 多对象 多任务

import time


def sing():
    '''唱歌 5秒'''
    for i in range(5):
        print('---正在唱歌----')
        time.sleep(1)  # 延时一秒


def dance():
    '''跳舞 5秒'''
    for i in range(5):
        print('---正在跳舞---')
        time.sleep(1)  # 延时一秒


def main():
    sing()
    dance()


if __name__ == '__main__':
    main()

结果:

D:\Users\sunny\Anaconda3\python.exe F:/python学习/01-python基础/hm_01_hello.py
---正在唱歌----
---正在唱歌----
---正在唱歌----
---正在唱歌----
---正在唱歌----
---正在跳舞---
---正在跳舞---
---正在跳舞---
---正在跳舞---
---正在跳舞---

Process finished with exit code 0

这个代码总共执行了10秒,且唱歌和跳舞不能同时进行。
改进一下,让唱歌和跳舞同时进行

import time
import threading


def sing():
    '''唱歌 5秒'''
    for i in range(5):
        print('---正在唱歌----')
        time.sleep(1)  # 延时一秒


def dance():
    '''跳舞 5秒'''
    for i in range(5):
        print('---正在跳舞---')
        time.sleep(1)  # 延时一秒


def main():
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
    t1.start()
    t2.start()

if __name__ == '__main__':
    main()

运行结果:

D:\Users\sunny\Anaconda3\python.exe F:/python学习/01-python基础/hm_01_hello.py
---正在唱歌----
---正在跳舞---
---正在跳舞---
---正在唱歌----
---正在唱歌----
---正在跳舞---
---正在跳舞---
---正在唱歌----
---正在唱歌----
---正在跳舞---

Process finished with exit code 0

总共运行了5秒。这就涉及了 多任务。

并行:真的多任务
并发:假的多任务。任务数大于cpu 核数。
大部分都是并发。因为电脑上同时运行的任务数一般都是大于你的电脑核数的(现在基本是4核)

一个程序运行起来之后,一定有一个执行代码的东西,这个东西就被称为 线程

查看线程数量threading.enumerate()

程序如下所示:

import time
import threading


def sing():
    '''唱歌 5秒'''
    for i in range(5):
        print('---正在唱歌----')


def dance():
    '''跳舞 5秒'''
    for i in range(5):
        print('---正在跳舞---')


def main():
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)

    t1.start()
    time.sleep(1)
    print('----1-----')

    t2.start()
    time.sleep(1)
    print ('-----2----')

    print (threading.enumerate())

if __name__ == '__main__':
    main()

运行结果:

D:\Users\sunny\Anaconda3\python.exe F:/python学习/01-python基础/hm_01_hello.py
---正在唱歌----
---正在唱歌----
---正在唱歌----
---正在唱歌----
---正在唱歌----
----1-----
---正在跳舞---
---正在跳舞---
---正在跳舞---
---正在跳舞---
---正在跳舞---
-----2----
[<_MainThread(MainThread, started 15664)>]

Process finished with exit code 0

线程的运行是没有先后顺序的。所以必须加延时。
这里有三个线程,一个主线程,查看线程数量threading.enumerate(),两个子线程:唱歌 和 跳舞 。

import time
import threading


def sing():
    '''唱歌 2秒'''
    for i in range(2):
        print('---正在唱歌----')
        time.sleep(1)


def dance():
    '''跳舞 5秒'''
    for i in range(5):
        print('---正在跳舞---')
        time.sleep(1)


def main():
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
    t1.start()
    t2.start()

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

if __name__ == '__main__':
    main()

运行结果:

D:\Users\sunny\Anaconda3\python.exe F:/python学习/01-python基础/hm_01_hello.py
---正在唱歌----
---正在跳舞---
[<Thread(Thread-2, started 20804)>, <Thread(Thread-1, started 59508)>, <_MainThread(MainThread, started 34292)>]
---正在跳舞---
---正在唱歌----
[<Thread(Thread-2, started 20804)>, <Thread(Thread-1, started 59508)>, <_MainThread(MainThread, started 34292)>]
---正在跳舞---
[<Thread(Thread-2, started 20804)>, <Thread(Thread-1, started 59508)>, <_MainThread(MainThread, started 34292)>]
[<Thread(Thread-2, started 20804)>, <_MainThread(MainThread, started 34292)>]
---正在跳舞---
---正在跳舞---
[<Thread(Thread-2, started 20804)>, <_MainThread(MainThread, started 34292)>]
[<_MainThread(MainThread, started 34292)>]

Process finished with exit code 0

在这里程序中,把 唱歌 设置为2秒,跳舞设置为 5秒。看结果 ,我们可以看到,前两秒,有三个线程。2秒到5秒,有两个线程。5秒之后 ,只有一个主线程,这时跳出循环,程序结束。
所有 ,主线程运行时间 必须大于 子线程运行时间,要不然程序会死掉。

当创建Thread的时候,不会创建 线程
当调用Thread 创建出来的实例对象的start 方法的时候,才会创建线程以及这个线程开始运行。

定义一个线程,上文中的程序用的方法都是threading.Thread(target=函数名)

其实如果这个线程比较复杂,我们还可以使用类和继承,形式如下:
在这里插入图片描述
调用start 方法时,run 函数里写的什么,就运行什么。
当一个线程比较复杂,且分为多个函数来做的时候,最好用类的方法。

import threading


class MyThread(threading.Thread):
	def run(self):
		pass
	def login(self):
		pass
	def register(self):
		pass
		
		
if __name__ == '__main__':
	t = MyThread()
	t.start()

这个程序,当 start时,运行的是子线程中的 run 函数中的内容,那么,其他两个函数怎么调用呢?在主程序中吗?肯定不是。
应该在类中,自己调用。已达到使用 start 方法能够调用run 函数和其他两个函数。
在类中 这样定义;

self.login()
self.register()

主线程会等待所有线程结束之后再结束。
线程共享全局变量

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

a = 1
b = [1,2]

def test():
    global a
    a += 1

def test2():
    b.append(3)


print('a: %d' % a )
print( b )

test()
test2()

print('a: %d' % a )
print( b )

在这里插入图片描述
现在看一下,线程之间的全局变量是不是共享的:

import threading
import time


a = 1


def test1():
    global a
    a += 1
    print('---in test1 a=%d---' % a)

def test2():
    print('---in test2 a=%d---' % a)


def main():
    t1 = threading.Thread(target=test1)
    t2 = threading.Thread(target=test2)

    t1.start()
    time.sleep(1)

    t2.start()
    time.sleep(1)

    print('---in main thread a=%d' % a)
if __name__ == '__main__':
    main()

在这里插入图片描述
可以看出,线程之间的全局变量 是 共享的

现在我们用元组来进行测试,这里再补充一个知识点:线程创建实例时,不仅可以指定函数名 target=***, 还可以指定传递的参数 args=*** 。但是传递的必须是一个元组。

import threading
import time


def test1(a):
    a.append(3)
    print('---in test1 a=%s---' % str(a))


def test2(a):
    print('---in test2 a=%s---' % str(a))


a = [1,2]

def main():
    t1 = threading.Thread(target=test1, args=(a,))  # 必须传递一个元组
    t2 = threading.Thread(target=test2, args=(a,))

    t1.start()
    time.sleep(1)

    t2.start()
    time.sleep(1)

    print('---in main thread a=%s' % str(a))
if __name__ == '__main__':
    main()

在这里插入图片描述
在抓取数据的过程中,肯定是多线程,一个线程负责抓取数据,一个线程负责处理数据,一个线程负责把数据传递给客户。这三个线程之间的数据必须是共享全局变量的。

但是全局变量共享是不是有什么危害呢? 会造成资源竞争。

import threading
import time

a = 0

def test1(num):
    global a
    for i in range(num):
        a += 1
    print('---in test1 a=%d---' % a)


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


def main():
    t1 = threading.Thread(target=test1, args=(100,))  # 必须传递一个元组
    t2 = threading.Thread(target=test2, args=(100,))

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

    print('---in main thread a=%d' % a)


if __name__ == '__main__':
    main()

在这里插入图片描述
test1 将 a+ 1 执行 100 次,
test2 将 a + 1 执行 100 次,
由于线程之间没有先后顺序,但是结果肯定是加了200.所以上面的结果是对的。下面我们将循环次数加大:

    t1 = threading.Thread(target=test1, args=(1000000,))  # 必须传递一个元组
    t2 = threading.Thread(target=test2, args=(1000000,))

结果为:
在这里插入图片描述
按理说结果应该为:2000000 。这就是因为资源竞争而导致的问题。
在这里插入图片描述
那么怎么解决:使用同步或者异步方法。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
下面使用互斥锁解决资源竞争的问题:

import threading
import time

a = 0

def test1(num):
    global a
    # 上锁,如果之前没有上锁,那么此时直接上锁
    #如果此时已经上锁了,那么就会被堵塞在这里,直到这个锁被释放。
    mutex.acquire()
    for i in range(num):
        a += 1
    # 解锁
    mutex.release()
    print('---in test1 a=%d---' % a)


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

# 创建一个互斥锁
mutex = threading.Lock()


def main():
    t1 = threading.Thread(target=test1, args=(1000000,))  # 必须传递一个元组
    t2 = threading.Thread(target=test2, args=(1000000,))

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

    print('---in main thread a=%d' % a)


if __name__ == '__main__':
    main()

结果如下:

在这里插入图片描述
这是一种解决方案,先让test1 执行完,再去执行test2 ,这样就会导致,如果线程1运行时间是100秒,就会等待上锁100秒。直到执行完解锁,线程2才会被执行。所以,我们上锁的程序应该是越少越好。再看另一种解决方案,

import threading
import time

a = 0

def test1(num):
    global a

    for i in range(num):
        mutex.acquire()
        a += 1
        mutex.release()
    print('---in test1 a=%d---' % a)


def test2(num):
    global a

    for i in range(num):
        mutex.acquire()
        a += 1
        mutex.release()
    print('---in test2 a=%d---' % a)

# 创建一个互斥锁
mutex = threading.Lock()


def main():
    t1 = threading.Thread(target=test1, args=(1000000,))  # 必须传递一个元组
    t2 = threading.Thread(target=test2, args=(1000000,))

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

    print('---in main thread a=%d' % a)


if __name__ == '__main__':
    main()

运行结果:
在这里插入图片描述
在这里插入图片描述
两次运行结果对比可以看出:最终都是加了2000000,但是两个子线程的结果却不一样。是因为,第一种上锁是加完1000000,再加1000000。所以结果固定。第二种上锁,可能是线程1先上锁,可能执行一次以后解锁,线程2 抢到锁,执行2次解锁,然后线程1再上锁… 因为是随机的,所以结果不固定,但是总次数2000000是固定的。

一个程序里可以有多个线程,一个线程里可以有多个函数,那么一个线程里可以有多个锁吗?可以!但是多锁容易出现问题。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
创建了两个类,其中各有一个锁,A B 。A B 都可以上锁,但是A B 都在等对方解锁,就会卡在这里。出现死锁。
在这里插入图片描述
银行家算法:
在这里插入图片描述
在这里插入图片描述

import socket
import threading

def send_masg(udp_socket, dest_ip, dest_port):
    '''发送数据'''

    while True:
        recv_data = input('请输入要发送的信息:')
        udp_socket.sendto(recv_data.encode('utf-8'), (dest_ip, dest_port))


def recv_masg(udp_socket):
    '''接收数据并显示'''

    while True:
        recv_data  = udp_socket.recvfrom(1024)
        print(recv_data)


def main():
    ''' 完成 udp 聊天器的整体控制'''

    # 1. 创建套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    # 2. 绑定本地信息
    udp_socket.bind(('',7890))

    # 3. 获取对方信息
    dest_ip = input('请输入对方的ip:')
    dest_port = int(input('请输入对方的port:'))

    # 创建两个线程,接受信息。
    t1 = threading.Thread(target=send_masg, args=(udp_socket,dest_ip, dest_port))
    t2 = threading.Thread(target=recv_masg, args=(udp_socket, ))

    t1.start()
    t2.start()

if __name__ == '__main__':
    main()

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值