多线程

多进程: 核心是多道技术,本质上就是切换加保存技术。 当进程IO操作较多,可以提高程序效率。 每个进程都默认有一条主线程。

多线程: 程序的执行线路,相当于一条流水线,其包含了程序的具体执行步骤。 操作系统是工厂,进程就是车间,线程就是流水线。 同一个进程的线程PID相同

线程和进程的关系: 进程包含了运行程序的所有资源,同一进程内的线程们共享该资源。不同进程内的线程资源是隔离的。 进程是一个资源单位,线程是CPU的最小执行单位! 每一个进程一旦被创建,就默认开启了一条线程,该线程称之为主线程。 一个进程可以包含多个线程。进程包含线程,线程依赖进程。

为什么使用线程: 提高程序效率,进程对操作系统的资源占用较高。 线程是如何提高效率的: 默认情况下,每个进程有且只有一条主线程,执行代码时如果遇到IO,系统就会切换到其他应用程序(其实是切换线程),这会降低当前应用程序的效率,CPU切换是在不同线程之间进行切换,多线程可以使CPU在一个进程内切换,从而提高程序对CPU的占用率。

什么时候启用多线程: 当程序遇到IO时 但如果程序是纯计算时,使用多线程无法提高效率。

进程和线程的区别: 进程对于操作系统的资源占用非常高,而线程比较低(是进程的十分之一到百分之一) 同一个进程内的线程共享该进程的资源

如何使用多线程: 开启线程的两种方式: 1、实例化Thread类 2、继承Thread类,覆盖run方法

# 实例化Thread
from threading import Thread

def task():
   print("threading run")
t1 = Thread(target=task)  # 形式与进程类似
t1.start()  # 手动启动线程,不用像进程那样放到main下
print("over") # 子线程可能更快,也可能慢

# 继承Thread类,覆盖run方法
class MyThread(Thread):
   def run(self):
       print("子线程 run")

MyThread().start()
print("over")

线程与进程的资源占用对比:

from multiprocessing import Process
from threading import Thread
import time


def task():
   print("子进程run")

if __name__ == '__main__':
   start = time.time()
   ps = []
   for i in range(100):
       p = Process(target=task)
       p.start()
       ps.append(p)
   for p in ps:
       p.join()  # 这一步是为了让所有子进程都执行完毕后在执行主进程,进而得到准确的运行时间
   print(time.time()-start)  #我的电脑用了74s

start = time.time()
ts = []
for i in range(100):
   t = Thread(target=task)
   t.start()
   ts.append(t)
for t in ts:
   t.join()  # 子线程都结束后再执行主线程
print(time.time() - start)  # 0.05s 效率提升了上千倍

同一进程内的线程资源共享

from threading import Thread
import time

x = 100
def task():
   global x
   x = 0
t = Thread(target=task)
t.start()
time.sleep(0.1) # 子线程先运行
print(x)  # 输出为0

from threading import Thread
import time

x = 100
def task():
   global x
   time.sleep(0.1) # 主线程先运行
   x = 0
t = Thread(target=task)
t.start()
print(x)  # 输出为100

守护线程

守护线程:在所有非守护线程结束后结束的线程。 一个程序中可以有多个守护线程,非守护线程都结束后,多个守护进程一起死。

# 调节下面的两处时间,可以得到不同的结果
# 两处时间都取消时,守护进程可能也会完全打印,主要是因为主线程在打印结束后需要时间去结束(并不是说只要主线程打印结束立马主线程就完全结束,结束也是需要时间的),而线程速度极快,所以可能完全执行。当然,也可能部分执行,这取决于CPU的运行情况。
from threading import Thread
import time

def task():
   print("子线程运行。。。")
   # time.sleep(1)
   print("子线程运行。。。")
t = Thread(target=task)
t.setDaemon(True)  # 要放到子线程开始前,注意进程的守护进程是deamon,线程的守护线程是setDeamon
t.start()
# time.sleep(0.1)
print("over")

线程中常用属性

from threading import Thread,current_thread,enumerate,active_count

import os

def task():
   print("running...")
   print("子线程",os.getpid())  # 主线程和子线程PID相同
   print(current_thread())  # 获取当前线程对象
   print(active_count())  # 获取正在运行的线程个数

t1 = Thread(target=task)
t1.start()
print("主线程",os.getpid())

print(t1.is_alive())  # t1线程是否还在运行
print(t1.isAlive())   # 同上,只是两种不同写法,官方文档中显示一模一样:isAlive = is_alive
print(t1.getName())  # 获取子线程t1的线程名称(Thread-1)(即第一个子线程)

print(enumerate())   # 获取所有线程对象列表
输出结果:
[<_MainThread(MainThread, started 19516)>, <Thread(Thread-1, started 9072)>]

线程互斥锁

当多个线程需要同时修改同一份数据时,可能会造成数据错乱,此时需要互斥锁。(类似于进程互斥锁)

不用线程互斥锁的情况:

a = 100
from threading import Thread
import time
def task():
   global a
   temp = a - 1
   time.sleep(0.01)  # 强行增加子线程运行时间,由于没有互斥锁,主线程的a会错乱
   a = temp  # 故意增加步骤,延长线程运行时间
for i in range(100):
   t = Thread(target=task)
   t.start()

print(a) # 输出结果可能为98或99等等(都有可能)

解决办法,加入线程互斥锁,并且添加join确保子线程全部运行结束:

a = 100
from threading import Thread,Lock
import time
lock = Lock()
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()  # 确保所有子进程都结束

# t.join() # 使用此方法不行,因为这样只能等待最后一个子线程,但是无法保证最后一个子线程是最后执行的,如果最后一个子线程却先于一些线程执行完毕,那么还是会出问题。
print(a)  # 结果为0

信号量

信号量也是一种锁,特点是可以设置一个数据可以被几个线程(进程)共享。

与Lock的区别:
  Lock锁,数据同时只能被一个线程使用
  信号量,可以指定数据同时被多个线程使用

使用场景:
  限制一个数据被同时访问的次数,保证程序/操作系统的稳定
  比如一台电脑设置共享,限制连接的终端,保证CPU/磁盘不至于过载导致错乱或崩溃

例子:

from threading import Semaphore,Thread,current_thread
import time

sem = Semaphore(3)  # 信号量设定为3,一份数据最多允许三份子线程访问

def task():
   sem.acquire()  # 和锁的用法类似
   print("%s run" %current_thread())
   time.sleep(1)
   sem.release()

for i in range(10):
   t = Thread(target=task)
   t.start()
   
输出结果:(三个三个一输出)
<Thread(Thread-1, started 13944)> run
<Thread(Thread-2, started 27096)> run
<Thread(Thread-3, started 26108)> run
<Thread(Thread-4, started 20580)> run
<Thread(Thread-5, started 20304)> run
<Thread(Thread-6, started 10676)> run
<Thread(Thread-8, started 18256)> run
<Thread(Thread-7, started 22684)> run
<Thread(Thread-9, started 18532)> run
<Thread(Thread-10, started 7036)> run

生产者消费者+守护进程

应用:生产者和消费者通过队列交换数据,都结束后程序再结束。

这里先补充一个其他知识:

进程队列的另一个类JoinableQueue

JoinableQueue同样通过multiprocessing使用。

创建队列的另外一个类:

JoinableQueue([maxsize]):这就像是一个Queue对象,但队列允许项目的使用者通知生成者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。

参数介绍:

maxsize是队列中允许最大项数,省略则无大小限制。

方法介绍:

JoinableQueue的实例p除了与Queue对象相同的方法之外还具有:

q.task_done():使用者使用此方法发出信号,表示q.get()的返回项目已经被处理。如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常

q.join():生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用q.task_done()方法为止

简言之:(如果还不理解看最下面的官方文档)

task_done()表示队列中的某个任务已经完成了,会返回给join一个信号。 join()会一直阻塞直到队列中的所有任务已经被获取并处理。一旦任务被加入队列,任务计数会加1,一旦收到task_done()的返回信号,任务计数会减1,如果任务计数减为0,则join()解除阻塞,程序也就能继续运行了。

import time,random
from multiprocessing import JoinableQueue,Process


def eat_hotdog(name,q):
   while True:  # 因为是守护进程,所以只要主进程结束,该循环也就结束了。
       res = q.get()
       print("%s 在吃 %s" %(name,res))
       time.sleep(random.randint(1,2))
       q.task_done()  # 记录当前已经被处理的数据的数量,该方法是JoinableQueue下的方法。


def make_hotdog(name,q):
   for i in range(1,5):
       res = "第%s热狗" %i
       print("%s 生产了 %s" % (name, res))
       q.put(res)


if __name__ == '__main__':
   q = JoinableQueue()
   p1 = Process(target=make_hotdog,args=("徐福记热狗店",q))
   p1.start()

   p3 = Process(target=make_hotdog,args=("万达热狗店",q))
   p3.start()

   p2 = Process(target=eat_hotdog,args=("思聪",q))
   p2.daemon = True  # 此处加入守护进程,确保主程序结束后,该进程的while循环也会结束
   p2.start()

# 生产者全部生产完成
   p1.join()
   p3.join()

# 保证队列为空,全部被处理
   q.join()  # 该join方法是JoinableQueue下的方法,并不是上面的那种join方法
   
输出结果:
徐福记热狗店 生产了 第1热狗
徐福记热狗店 生产了 第2热狗
徐福记热狗店 生产了 第3热狗
徐福记热狗店 生产了 第4热狗
万达热狗店 生产了 第1热狗
万达热狗店 生产了 第2热狗
万达热狗店 生产了 第3热狗
万达热狗店 生产了 第4热狗
思聪 在吃 第1热狗
思聪 在吃 第2热狗
思聪 在吃 第3热狗
思聪 在吃 第4热狗
思聪 在吃 第1热狗
思聪 在吃 第2热狗
思聪 在吃 第3热狗
思聪 在吃 第4热狗

官方文档:

JoinableQueue是由Queue派生出来的一个类,与Queue不同,JoinableQueue有task_done()和Join()方法。

task_done()
Indicate that a formerly enqueued task is complete. Used by queue consumer threads. For each get() used to fetch a task, a subsequent call to task_done()tells the queue that the processing on the task is complete.
If a join() is currently blocking, it will resume when all items have been processed (meaning that a task_done() call was received for every item that had been put() into the queue).
Raises a ValueError if called more times than there were items placed in the queue.

join()
Block until all items in the queue have been gotten and processed.
The count of unfinished tasks goes up whenever an item is added to the queue. The count goes down whenever a consumer thread calls task_done() to indicate that the item was retrieved and all work on it is complete. When the count of unfinished tasks drops to zero, join() unblocks.

20190102(多线程,守护线程,线程互斥锁,信号量,JoinableQueue)的更多相关文章

  1. JoinableQueue队列,线程,线程于进程的关系,使用线程,线程的特点,守护线程,线程的互斥锁,死锁问题,递归锁,信号量

    1.JoinableQueue队列 JoinableQueue([maxsize]):这就像是一个Queue对象,但是队列允许项目的使用者通知生成者项目已经被成功处理.通知进程是使用共享的信号和条件变 ...

  2. 网络编程并发 多进程 进程池,互斥锁,信号量,IO模型

    进程:程序正在执行的过程,就是一个正在执行的任务,而负责执行任务的就是cpu 操作系统:操作系统就是一个协调.管理和控制计算机硬件资源和软件资源的控制程序. 操作系统的作用: 1:隐藏丑陋复杂的硬件接 ...

  3. 并发编程(二)——利用Process类开启进程、僵尸进程、孤儿进程、守护进程、互斥锁、队列与管道

    Process类与开启进程.守护进程.互斥锁 一.multiprocessing模块 1.multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模 ...

  4. [并发编程 - socketserver模块实现并发、[进程查看父子进程pid、僵尸进程、孤儿进程、守护进程、互斥锁、队列、生产者消费者模型]

    [并发编程 - socketserver模块实现并发.[进程查看父子进程pid.僵尸进程.孤儿进程.守护进程.互斥锁.队列.生产者消费者模型] socketserver模块实现并发 基于tcp的套接字 ...

  5. day35 守护进程、互斥锁、IPC

    day35 守护进程.互斥锁.IPC 1.守护进程 # 守护进程:当父进程执行完毕后,设置的守护进程也会跟着结束# 当一个进程被设置为守护进程后,其不能再产生子进程​ from multiproces ...

  6. day36 joinablequeue、多线程理论、多线程的两种使用方式、守护线程、互斥锁、死锁、递归锁、信号量

    1.joinablequeue队列 joinablequeue与queue一样,也是一种队列,其继承自queue,也有queue中的put 与get 方法,但是在joinablequeue中有自己的 ...

  7. day34 python学习 守护进程,线程,互斥锁,信号量,生产者消费者模型,

    六 守护线程 无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁 需要强调的是:运行完毕并非终止运行 #1.对主进程来说,运行完毕指的是主进程代码运行完毕 #2.对主线程来说,运行完 ...

  8. 多进程(了解):守护进程,互斥锁,信号量,进程Queue与线程queue(生产者与消费者模型)

    一.守护进程 主进程创建守护进程,守护进程的主要的特征为:①守护进程会在主进程代码执行结束时立即终止:②守护进程内无法继续再开子进程,否则会抛出异常. 实例: from multiprocessing ...

  9. 并发编程 - 线程 - 1.互斥锁/2.GIL解释器锁/3.死锁与递归锁/4.信号量/5.Event事件/6.定时器

    1.互斥锁: 原理:将并行变成串行 精髓:局部串行,只针对共享数据修改 保护不同的数据就应该用不用的锁 from threading import Thread, Lock import time n ...

  10. Python进阶(3)_进程与线程中的lock(线程中互斥锁、递归锁、信号量、Event对象、队列queue)

    1.同步锁 (Lock) 当全局资源(counter)被抢占的情况,问题产生的原因就是没有控制多个线程对同一资源的访问,对数据造成破坏,使得线程运行的结果不可预期.这种现象称为“线程不安全”.在开发过 ...

随机推荐

  1. IDEA使用汇总

    1. 常用配置 File --> Settings (Ctrl + Alt + S) 1).提示不区分大小写: Editor-->Genereal-->Code Completion ...

  2. android 开发-系统设置界面的实现

    具体与Preference的用法类似,这里就不做过多解释,直接贴示例代码,需要在res下新建xml文件夹,在xml文件夹下添加xml文件. xml:(注意:root节点是:PreferenceScre ...

  3. 使用AlarmManager定期执行工作

    新建一个Service来模拟后台执行的程序,PollingService.java: package com.ryantang.rtpollingdemo; import android.app.No ...

  4. Android客户端与PC服务端、android服务端通过WiFi通信

    前期准备:我的是Linux Mint操作系统(总之折腾的过程中怀疑过是不是系统的问题),首先是要创建wifi热点给android手机使用,这个时候笔记本作为通信的服务器端,android手机作为客户端 ...

  5. 《大话设计模式》num02---策略模式

    2018年01月22日 22:04:57 独行侠的守望 阅读数:72更多个人分类: 设计模式编辑版权声明:本文为博主原创文章,转载请注明文章链接. https://blog.csdn.net/xiao ...

  6. JAVA4大线程池

    不知不觉中我们电脑的硬件设施越来越好,从双核四线程普及到如今四核八线比比皆是.互联网发展至今,讲究的就是快,less is more,而且大数据的诞生和各种种类繁多的需求处理,单线程的程序逐渐不能满足 ...

  7. 浅谈.htaccess文件--避免滥用.htaccess文件

    .htaccess文件提供了一种目录级别的修改配置的方式. NOTE: 如果你拥有修改apache配置文件的权限,那么完全没有必要使用.htaccess文件.使用.htaccess文件会拖慢apach ...

  8. ps使用

    1.图片剪裁 1.按快捷键M(矩形选择工具)-> 选中要扣出的图片(按shift可正方形)->按快捷键C(剪裁工具)->双击鼠标选中区域,剪裁成功. 2.选中psd中的图标 1.按快 ...

  9. linux 下源码编译环境配置

    yum install -y apr* autoconf automake bison bzip2 bzip2* compat* cpp curl curl-devel \ fontconfig fo ...

  10. LeetCode Best Time to Buy and Sell Stock II (简单题)

    题意: 股票买卖第2题.给出每天的股票价格,每次最多买一股,可以多次操作,但是每次在买之前必须保证身上无股票.问最大的利润? 思路: 每天的股票价格可以看成是一条曲线,能卖掉就卖掉,那么肯定是在上升的 ...