并发编程之线程

多进程

核心--多道技术

切换+保存状态

程序I/O操作较多时,可提高程序效率

 

多线程

概念:线程是应用程序中工作的最小单元,或者又称之为微进程。 

进程是一个资源单位,包含运行程序所需的所有资源,线程才是CPU上的执行单位

每个进程一旦被创建,就默认开启了一条线程,其为主线程,有且只有一个。

执行代码时,如遇I/O系统会自动切换到其他应用程序--降低了当前应用程序的效率。若进程中不仅有主线还有其他线程,主线遇到I/O时--切到其他线程,保证了应用程序对CPU利用率(占用率)

对于CPU而言,只有线程可以被执行,所以CPU切换是在不同线程之间进行切换,开启多线程可提高效率---

多线程即在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间

创建进程的开销要远大于线程

进程之间是竞争关系,线程之间是协作关系

使用线程:提高效率

为何不用多进程--进程对操作系统的资源耗费非常高

线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。

指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。

  • 线程可以被抢占(中断)。
  • 在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) -- 这就是线程的退让。

线程可以分为:

  • 内核线程:由操作系统内核创建和撤销。
  • 用户线程:不需要内核支持而在用户程序中实现的线程。

使用:

  • threading(推荐使用)

thread 模块已被废弃。用户可以使用 threading 模块代替。所以,在 Python3 中不能再使用"thread" 模块。为了兼容性,Python3 将 thread 重命名为 "_thread"。反正你不要再用就是了! 我习惯上用 from threading import Thread

两种开启线程的方式

1.实例化Thread

2.继承Thread类,覆盖run方法

# 开启进程第一种方式
from threading import Thread

def task():
    print("threading running")
t1=Thread(target=task)
t1.start()
print("over")


# 第二种方式
class MyThread(Thread):
    def run(self):
        print("子线程 running...")

MyThrestart()
print("over2")

当程序中遇到I/O时-------开启多线程,当程序中为纯计算任务时-----无法提高效率

多线程与多进程区别: 

1.多个线程共享创建它的进程的地址空间,资源共享;进程有自己的地址空间。
2.线程可以直接访问其进程的数据段;进程有它们自己的父进程数据段的副本。
3.线程可以直接与其进程的其他线程通信;进程必须使用进程间通信来与同级进程通信。 
4.新线程很容易创建;新进程需要重复父进程。
5.线程可以对同一进程的线程进行相当大的控制;进程只能对子进程进行控制。
6.对主线程的更改(取消、优先级更改等)可能会影响进程的其他线程的行为;对父进程的更改不会影响子进程。
进程对操作系统的资源耗费非常高,线程非常低
进程数据相互隔离,同一进程中线程数据共享
IO密集的程序 ---多线程---IO反正都要被阻塞,切换 线程的方式不会比进程慢多少
计算密集的程序 ---多进程 ---利用多核可以达到并行!

时间区别:

# 开启进程第一种方式
from threading import Thread

def task():
    print("threading running")
t1=Thread(target=task)
t1.start()
print("over")


# 对比进程启动时间
from multiprocessing import Process

def task():
    print("running")

if __name__ == '__main__':
    t2=Process(target=task)
    t2.start()
    print("over...")

守护线程(setDaemon)

在(同进程下)所有非守护线程结束后结束


守护进程使用--生产者消费者模型

one)

if __name__ == '__main__':
    q = JoinableQueue()
    #生产者1
    c1 = Process(target=make_hotdog,args=("万达热狗店",q))
    c1.start()

    #生产者2
    c2 = Process(target=make_hotdog, args=("老男孩热狗店", q))
    c2.start()

    # 消费者
    p2 = Process(target=eat_hotdog,args=("思聪",q))

    p2.start()


    # 首先保证生产者全部产完成
    c1.join()
    c2.join()

    # 保证队列中的数据全部被处理了
    q.join() # 明确生产方已经不会再生成数据了
View Code

Thread方法和一些常用属性

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

除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:
run(): 用以表示线程活动的方法。
start():启动线程活动。 
join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
setDaemon(True):守护主线程,跟随主线程退(必须要放在start()上方)
isAlive(): 返回线程是否活动的。
getName(): 返回线程名。
setName(): 设置线程名。
 
View Code
# 获取所有线程对象,返回一个列表
print(enumerate())
# 获取当前正在运行的线程个数
print(active_count())
# 获取当前线程对象
print(current_thread())

加锁

什么时候用锁 当多个进程或多个线程需要同时修改同一份数据时,可能会造成数据的错乱,所以必须得加锁

互斥锁

多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。

使用 Thread 对象的 Lock 和 Rlock 可以实现简单的线程互斥。

这两个对象都有 acquire 方法和 release 方法。

对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire 和 release 方法之间。

import time
from threading import Thread,Lock

lock =Lock()
a = 100

def task():
    lock.acquire()
    global a
    temp = a - 1
    time.sleep(0.01)
    a = temp
    lock.release()

ts = []
for i in range(100):
    t = Thread(target=task)
    t.start()
    ts.append(t)
for t in ts:
    t.join()

print(a)
View Code

死锁---与进程无区别

Rlock--可重用锁

信号量 BoundedSemaphore或Semaphore

其实也是一种互斥(锁),特点是可以设置一个数据可以被一个线程/进程共享,即限制线程的并发量

与普通锁区别:

普通锁一旦加锁,则这个数据同一时间只能被一个线程使用

信号量 可以让这个数据在同一时间只能被多个线程使用

使用场景:

限制一个数据被同时的访问的次数,保证程序正常运行

import time
from threading import  Semaphore,Thread,current_thread

sem=Semaphore(3)  # 指定有几个线程共享

def task():
    sem.acquire()  # 先加锁
    print("%s run..."% current_thread())
    time.sleep(3)
    sem.release()

for i in range(10):
    t=Thread(target=task)
    t.start()
练习:
    1、基于多线程实现并发的套接字通信
​    2、基于多线程+TCP协议实现上传下载功能
​    3、编写一个文本处理工具,总共三个任务,一个接收用户输入,一个将用户输入的内容格式化成大写,一个将格式化后的结果存入文件    

练习

1、基于多线程实现并发的套接字通信

2、基于多线程+TCP协议实现上传下载功能

3、编写一个文本处理工具,总共三个任务,一个接收用户输入,一个将用户输入的内容格式化成大写,一个将格式化后的结果存入文件

 

import socket

client = socket.socket()
client.connect(("127.0.0.1", 9090))

while True:
    msg = input(">>>:").strip()
    if not msg:
        continue
    client.send(msg.encode("utf-8"))



import socket
from threading import Thread

server = socket.socket()

server.bind(("127.0.0.1", 9090))
server.listen(3)


def task(client):
    while True:
        try:
            data = client.recv(1024)
            print(data.decode("utf-8"))
            client.send(data.upper())

        except Exception as e:
            print(e)


if __name__ == '__main__':
    while True:
        client, addr = server.accept()
        t1 = Thread(target=task, args=(client,))
        t1.start()
        print("hi")
1、基于多线程实现并发的套接字通信
客户端:
import json
import socket
import struct
from threading import Thread

import common

is_uploading = False
is_downloading = False

c = socket.socket()
c.connect(("127.0.0.1", 9090))


def download():
    global is_downloading
    if is_downloading:
        print("您有一个任务正在下载")
        return

    is_downloading = True
    print("ddddd")
    head = {"func": "download"}
    head_data = json.dumps(head).encode("utf-8")
    c.send(struct.pack("i", len(head_data)))
    c.send(head_data)
    common.recv_file(c)

    is_downloading = False

    # head={"func":"download"}
    # head_data=json.dumps(head).encode("utf-8")
    # c.send(struct.pack("i",len(head_data)))
    # c.send(head_data)


def upload():
    global is_uploading
    if is_uploading:
        print("您有一个任务正在上传.....")
        return
    is_uploading = True

    head = {"func": "upload"}
    head_data = json.dumps(head).encode("utf-8")
    c.send(struct.pack("i", len(head_data)))
    c.send(head_data)

    common.send_file(c, r"E:2019.11.3 GILclient111.py", "b.txt")

    is_uploading = False

    # head = {"func": "download"}
    # head_data = json.dumps(head).encode("utf-8")
    # c.send(struct.pack("i", len(head_data)))
    # c.send(head_data)


func_dic = {"1": download, "2": upload}
while True:
    res = input("请选择  1:上传  2:下载  Q:退出").strip()
    if res.upper() == "Q":
        break
    if res not in func_dic:
        print("error")
        continue
    Thread(target=func_dic[res]).start()

服务器端:
from threading import Thread
import socket,json,struct
from concurrent.futures import  ThreadPoolExecutor
import common


server=socket.socket()
# server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(("127.0.0.1",9090))
server.listen(5)
pool=ThreadPoolExecutor()




def recv_file(c):
    common.recv_file(c)

    print("recv   ")

def send_file(c):
    common.send_file(c)

    print("send   ")



def task(c):
    """
    接收用户数据,判断用户要执行的操作
    :return:
    """
    # 先接收报头
    # c.recv(4),其为2进制需转
    while True:

        try:
            len_data=c.recv(4)
            if not len_data:
                c.close()  # 需加while ,否则发送完毕就断开链接
                break
            head_len=struct.unpack("i",len_data)[0]

            head=json.loads(c.recv(head_len).decode("utf-8"))
            if head["func"]=="download":
                send_file(c)
            elif head["func"]=="upload":
                recv_file(c)
            else:
                print("require error")
        except ConnectionResetError:
            c.close()
            break
2、基于多线程+TCP协议实现上传下载功能
from threading import Thread
import time
msg_l=[]
format_l=[]
def receive():
    while True:
        msg=input(">>>:").strip()
        if not msg:
            continue
        msg_l.append(msg)


def format_msg():
    while True:
        if msg_l:
            res=msg_l.pop(0)
            format_l.append(res.upper())

def save():
    with open("db.txt","a",encoding="utf-8")as f:
        while True:
            if format_l:
                res = format_l.pop(0)
                f.write("%s
")%res
            time.sleep(0.1)


if __name__ == '__main__':
    t1=Thread(target=receive)
    t2=Thread(target=format_msg)
    t3=Thread(target=save)

    t1.start()
    t2.start()
    t3.start()
3、编写一个文本处理工具,总共三个任务: # 一个接收用户输入,一个将用户输入的内容格式化成大写,一个将格式化后的结果存入文件
原文地址:https://www.cnblogs.com/dongzhihaoya/p/10210799.html