什么是进程
- 进程是执行中的程序。
 - 拥有独立地址空间,内存,数据栈等。
 - 操作系统统一管理。
 - 派生(fork或spawn)新进程。
 - 进程间通信(IPC)方式共享信息。
 
什么是线程
- 同进程下执行,并共享相同的上下文。
 - 线程间的信息共享和通信更加容易。
 - 多线程并发执行。
 - 需要同步原语。
 
python与线程
- 解释器主循环
 - 主循环中只有一个控制线程在执行。
 - 使用全局解释器锁(GIL)。
 
GIL保证一个线程
- 设置GIL
 - 切换进一个线程去运行
 - 执行下面的操作之一 
  
- 指定数量的字节码指令
 - 线程主动让出控制权
 
 - 把线程设置回睡眠状态(切换出线程)
 - 解锁GIL
 - 重复上述步骤
 
两种线程管理
Python提供了两种线程管理模式。
- _thread:提供了基本的线程和锁。
 - threading: 提供了更高级别,功能更全的线程管理。threading底层使用了_thread。 
  
- 支持同步机制
 - 支持守护线程
 
 
_thread模块
| 函数/方法 | 描述 | 
|---|---|
| thread模块的函数 | |
| start_new_thread(function,args,kwargs=None) | 派生一个新的线程,使用给定的args和可选的kwargs来执行function | 
| allocate_lock() | 分配LockType锁对象 | 
| LockType锁对象方法 | |
| acquire(wait=None) | 尝试获取锁对象 | 
| locked() | 如果获取了锁对象则返回True,否则返回False | 
| release() | 释放锁 | 
使用_thread模块案例1
代码:
import _thread
import logging
from time import sleep, ctime
# 配置日志收集器
logging.basicConfig(level=logging.INFO)
def loop0():
    """定义一个子方法1"""
    logging.info("start loop0 at " + ctime())
    sleep(4)
    logging.info("end loop0 at " + ctime())
def loop1():
    """定义一个子方法2"""
    logging.info("start loop1 at " + ctime())
    sleep(2)
    logging.info("end loop1 at " + ctime())
def main():
    """主方法"""
    logging.info("start all at " + ctime())
    # 运行子方法1
    _thread.start_new_thread(loop0, ())
    # 运行子方法2
    _thread.start_new_thread(loop1, ())
    sleep(6)
    logging.info("end all at " + ctime())
if __name__ == "__main__":
    main()
 
运行结果:
 
注意:这里主方法中必须加上time.sleep(6),否则主进程退出后,所有子进程都将强行退出,所有子线程将不被执行。这里也该模块的缺点之一,因为它没有守护线程的概念。
 以下结果是不加time.sleep(6)的效果。
 
使用_thread模块案例2-利用锁
上述案例中其实存在缺点。在实际应用中,我们并不确定主线程需要等待子线程几秒钟,子线程才能结束。所以本案例加入锁,可以有效的解决这一问题。
代码:
import _thread
import logging
from time import sleep, ctime
# 配置日志收集器
logging.basicConfig(level=logging.INFO)
loops = [2, 4]
def loop(nloop, nsec, lock):
    """
    定义一个子方法
    nloop: 用于标识当前loop处于第几个
    nsec: 时间,loop循环时间
    lock: 锁
    """
    logging.info(f"start loop {nloop} at " + ctime())
    sleep(nsec)
    logging.info(f"end loop {nloop} at " + ctime())
    # loop执行完成后释放锁
    lock.release()
def main():
    """主方法"""
    logging.info("start all at " + ctime())
    # 定义一个锁列表,可以包含很多的锁
    locks = []
    # loop的名字
    nloops = range(len(loops))
    for i in nloops:
        # 声明一个锁
        lock = _thread.allocate_lock()
        # 锁上锁
        lock.acquire()
        # 将这个锁追加到锁列表
        locks.append(lock)
        # 获取锁是需要时间的,因此获取锁和开启子线程不能放在同一个for循环中
        # 可以有效避免获取第2个锁的时间,第一个线程已经执行完毕了
        # _thread.start_new_thread(loop, (i, loops[i], locks[i]))
    for i in nloops:
        # 启动子线程
        _thread.start_new_thread(loop, (i, loops[i], locks[i]))
    for i in nloops:
        # 检查锁是否是锁定,如果被锁定就什么都不做;如果锁是释放的则退出循环
        # 也就是等待每个子线程结束后才退出主线程
        while locks[i].locked(): pass
    logging.info("end all at " + ctime())
if __name__ == "__main__":
    main()
 
运行结果:
 
threading模块
| 对象 | 描述 | 
|---|---|
| Thread | 表示一个执行线程的对象 | 
| Lock | 锁原语对象(和thread模块中的锁一样) | 
| RLock | 可重入锁对象,使单一线程可以(再次)获得已持有的锁(递归锁) | 
| Condition | 条件变量对象,使得一个线程等待另一个线程满足特定的“条件”,比如改变状态或某个数据值 | 
| Event | 条件变量的通用版本,任意数量的线程等待某个事件的发生,在该事件发生后所有线程将被激活 | 
| Semaphore | 为线程间共享的有限资源提供了一个“计数器”,如果没有可用资源时会被阻塞 | 
| BoundedSemaphore | 与Semaphore相似,不过它不允许超过初始值 | 
| Timer | 与Thread相似,不过它要在运行前等待一段时间 | 
| Barrier | 创建一个“障碍”,必须达到指定数量的线程后才可以继续 | 
使用threading模块案例
threading自带锁,可以更有效解决上述案例【使用_thread模块案例2-利用锁】的问题。
代码:
import threading
import logging
from time import sleep, ctime
# 配置日志收集器
logging.basicConfig(level=logging.INFO)
loops = [2, 4]
def loop(nloop, nsec):
    """
    定义一个子方法
    nloop: 用于标识当前loop处于第几个
    nsec: 时间,loop循环时间
    """
    logging.info(f"start loop {nloop} at " + ctime())
    sleep(nsec)
    logging.info(f"end loop {nloop} at " + ctime())
def main():
    """主方法"""
    logging.info("start all at " + ctime())
    # 声明一个线程组
    threads = []
    # loop的名字
    nloops = range(len(loops))
    for i in nloops:
        # 设置子线程
        t = threading.Thread(target=loop, args=(i, loops[i]))
        threads.append(t)
    for i in nloops:
        # 启动子线程
        threads[i].start()
    for i in nloops:
        # 将子线程加入到主线程中
        # 如果子进程执行完毕,主线程就会结束。
        # 如果子线程没有执行完成, 主线程就会阻塞,一定会等子线程执行完成
        threads[i].join()
    logging.info("end all at " + ctime())
if __name__ == "__main__":
    main()
 
运行结果:
 注意:loop 0 和loop 1的执行先后顺序不是一定的。
 
Thread类
| 属性 | 描述 | 
|---|---|
| Thread对象数据属性 | |
| name | 线程名 | 
| ident | 线程的标识符 | 
| daemon | 布尔标志,表示这个线程是否是守护线程 | 
| Thread对象方法 | |
| init(group=None, target=None, name=None, args=(), kwargs={}, verbose=None, daemon=None) | 实例化一个线程对象,需要有一个可调用的target,以及其参数args或kwargs。还可以传递name或group参数,不过后者还未实现。此外,verbose标志也是可接受的。而daemon的值将会设定thread.daemon属性/标志 | 
| start() | 开始执行该线程 | 
| run() | 定义线程功能的方法(通常在子类中被应用开发者重写) | 
| join(timeout=None) | 直至启动的线程终止之前一直挂起;除非给出了timeout(秒),否则会一直阻塞 | 
| getName() | 返回线程名 | 
| setName(name) | 设定线程名 | 
| isAlivel/is_alive() | 布尔标志,表示这个线程是否还存活 | 
| isDaemon() | 如果是守护线程,则返回True,否则返回False | 
| setDaemon(daemonic) | 把线程的守护标志设定为布尔值daemonic(必须在线程start()之前调用) | 
使用threading模块案例-重写threading.Thread
我们可以继续改进上述案例【使用threading模块案例】,使得代码更加符合面向对象编程的思想。
代码:
import threading
import logging
from time import sleep, ctime
# 配置日志收集器
logging.basicConfig(level=logging.INFO)
loops = [2, 4]
class MyThread(threading.Thread):
    def __init__(self, func, args, name=""):
        """ 主动调用threadding.Thread的初始构造方法 """
        threading.Thread.__init__(self)
        self.func = func
        self.args = args
        self.name = name
    def run(self) -> None:
        """重写父类的run方法"""
        self.func(*self.args)
def loop(nloop, nsec):
    """
    定义一个子方法
    nloop: 用于标识当前loop处于第几个
    nsec: 时间,loop循环时间
    """
    logging.info(f"start loop {nloop} at " + ctime())
    sleep(nsec)
    logging.info(f"end loop {nloop} at " + ctime())
def main():
    """主方法"""
    logging.info("start all at " + ctime())
    # 声明一个线程组
    threads = []
    # loop的名字
    nloops = range(len(loops))
    for i in nloops:
        # 设置子线程
        t = MyThread(loop, (i, loops[i]), loop.__name__)
        threads.append(t)
    for i in nloops:
        # 启动子线程
        threads[i].start()
    for i in nloops:
        # 将子线程加入到主线程中
        # 如果子进程执行完毕,主线程就会结束。
        # 如果子线程没有执行完成, 主线程就会阻塞,一定会等子线程执行完成
        threads[i].join()
    logging.info("end all at " + ctime())
if __name__ == "__main__":
    main()
 
运行结果:










