python多线程

文章介绍了线程作为CPU执行调度的基本单位,强调了线程与进程的关系,以及多线程在Python中的应用。由于GIL的存在,Python的多线程并不能充分利用多核优势。文章还讨论了线程锁的必要性,提供了一些线程同步和通信的解决方案,并给出多个Python多线程编程的示例,展示了线程的创建和执行方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程

CPU执行调度的最小单位。

不能独立存在,依赖进程存在。

一个进程至少有一个线程,叫做主线程,另外还有内核线程、用户线程。

线程之间共享内存。

线程之间的通信效率远高于进程间通信效率,线程之间切换代价也比进程小很多。

适用场景

Python的多线程适用于IO密集型任务。

多任务可以由多进程完成,也可以由一个进程内的多线程完成。

关系

进程是由若干线程组成的,一个进程至少有一个线程。

线程是通过一个进程派生出来的,进程结束,其派生的线程就全部死掉了,线程间共用进程的内存,线程奔溃也可能导致进程奔溃。

为什么需要线程锁

多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。

解决方案就是加锁。锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。

GIL--全局锁

GIL的全称是Global Interpreter Lock (全局解释器锁)。

Python解释器由于设计时有GIL全局锁,导致了多线程无法利用多核。

Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。

缺陷

频繁线程切换代价高。

线程安全(GIL)。

共享还是独享

如果某些资源不独享会导致线程运行错误,则该资源就由每个线程独享,而其他资源都由进程里面的所有线程共享。

线程共享资源:

地址空间、全局变量、打开的文件、子进程、闹铃、信号与信号服务程序、记账信息。

线程独享资源:

程序计数器、寄存器、栈、状态字。

python的多线程模块

Python的标准库提供了两个模块:thread和threading,thread是低级模块,threading是高级模块,对thread进行了封装。

绝大多数情况下,我们只需要使用threading这个高级模块。

多线程写法

1、导入模块

import threading

2、产生对象,传递的target参数是一个函数,即worker是一个函数;可以给线程传线程的name;可以给线程传参数args,agrs必须是一个元组。

t = threading.Thread(target=worker,name='lyz',args=(1,))

3、执行线程

t.start()

threading 模块提供的方法

threading.currentThread(): 返回当前的线程变量。

threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。

threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

run(): 用以表示线程活动的方法。

start():启动线程活动。

join(): 等待至线程中止。join()的作用是,在子线程完成运行之前,这个子线程的父线程将一直被阻塞。

isAlive(): 返回线程是否活动的。

getName(): 返回线程名。

setName(): 设置线程名。

多线程的注意事项

1、线程执行是无序的

2、生命周期:

python中,主线程会等待子线程结束之后才结束。

但是如果设置d.setDaemon(True)这个属性,则主线程结束,子线程就必须结束了。

3、阻塞:

如果你想让主线程等所有子线程执行完才执行主线程的任务:join()

t = threading.Thread(target=worker,name='lyz',args=(1,))

t.join()

4、默认情况下,多线程的无序性,操作全局变量是不安全的,所以有线程锁

from threading import Lock,Thread

然后生成锁对象:

lock = Lock()

申请锁:

lock.acquire()

释放锁:

lock.release()

代码例子

1:最简单的多线程例子

#!/usr/bin/env python
# encoding: UTF-8

import threading


def worker():

    print 'worker'

    return


for i in range(5):

    t = threading.Thread(target=worker)

    t.start()


执行结果:几乎同时打印worker,而不是循环一次才打印一次

[root@salt1 threading]# python t1.py

worker

worker

worker

worker

worker

2:带参数

#!/usr/bin/env python

# encoding: UTF-8


import threading


def worker(num, num2):

    print 'worker: %d %d' % (num, num2)

    return


for i in range(5):

    t = threading.Thread(target=worker, args=(i, i)) #args必须是一个元组

    t.start()


执行结果:

[root@salt1 threading]# python t2.py

worker: 1 1

worker: 2 2

worker: 3 3

worker: 4 4

3:多线程ping多个ip

import threading

import os


def ping(ip):

    os.system('ping -c 1 %s'%ip)


hostlist = ['192.168.50.20','192.168.50.24','192.168.50.13']


for i in hostlist:

    t = threading.Thread(target=ping,args=(i,))

    t.start()


执行结果:

[root@salt1 threading]# python t3.py

PING 192.168.50.20 (192.168.50.20) 56(84) bytes of data.

64 bytes from 192.168.50.20: icmp_seq=1 ttl=64 time=0.029 ms


--- 192.168.50.20 ping statistics ---

1 packets transmitted, 1 received, 0% packet loss, time 0ms

rtt min/avg/max/mdev = 0.029/0.029/0.029/0.000 ms

PING 192.168.50.24 (192.168.50.24) 56(84) bytes of data.

64 bytes from 192.168.50.24: icmp_seq=1 ttl=64 time=0.374 ms


--- 192.168.50.24 ping statistics ---

1 packets transmitted, 1 received, 0% packet loss, time 0ms

rtt min/avg/max/mdev = 0.374/0.374/0.374/0.000 ms

PING 192.168.50.13 (192.168.50.13) 56(84) bytes of data.

64 bytes from 192.168.50.13: icmp_seq=1 ttl=64 time=0.774 ms


--- 192.168.50.13 ping statistics ---

1 packets transmitted, 1 received, 0% packet loss, time 0ms

rtt min/avg/max/mdev = 0.774/0.774/0.774/0.000 ms

4:线程执行是无序的

#coding=utf8

import logging
import threading
import time


logging.basicConfig(level=logging.DEBUG,

                    format='[%(levelname)s](%(threadName)-10s) %(message)s',

                    )

def daemon():

    logging.debug('Starting')

    time.sleep(2)

    logging.debug('Exiting')

d = threading.Thread(name='daemon', target=daemon)

#但是如果设置d.setDaemon(True)这个属性,则主线程结束,子线程就结束了
#d.setDaemon(True)

def non_daemon():

    logging.debug('starting')

    logging.debug('exiting')

t = threading.Thread(name='non-daemon', target=non_daemon)

d.start()
t.start()

#如果你想让主线程等所有子线程执行完才执行主线程的任务:join()
#d.join()
#t.join()

print "all done!"

5:多线程的另外一种写法

#!/usr/bin/env python

import threading
import logging

logging.basicConfig(

                    level=logging.DEBUG,

                    format='(%(threadName)-10s) %(message)s',

                    )

#类的写法,父类必须是threading.Thread,类里边的函数名必须是run,覆盖父类里边的run
class MyThread(threading.Thread):
    def run(self):
        logging.debug('running')
        return

for i in range(5):
    t = MyThread()
    t.start()

6:同上例子

import threading
import logging

logging.basicConfig(level=logging.DEBUG,
                    format='(%(threadName)-10s) %(message)s',
                    )

class MyThreadWithArgs(threading.Thread):

    def __init__(self, group=None, target=None, name=None,

                 args=(), kwargs=None, verbose=None):

        #threading.Thread.__init__(self, group=group, target=target, name=name,
        super(MyThreadWithArgs, self).__init__()
        self.args = args
        self.kwargs = kwargs
        return

    def run(self):
        logging.debug('running with %s and %s', self.args, self.kwargs)
        return

for i in range(5):
    t = MyThreadWithArgs(args=(i,), kwargs={'a':'A', 'b':'B'})

t.start()

7:为什么需要线程锁

首先看没有线程锁的例子:

#!/usr/bin/env python
#-*- coding:utf-8 -*-


#race condition
from threading import Thread

var = 0

class incrementThread(Thread):

    def run(self):
        global var
        read_value = var
        print "var in %s is %d" % (self.name, var)
        var = read_value + 1
        print "var in %s after increment is %d" % (self.name, var)

def use_increment_thread():
    threads = []
    #尝试加大循环的次数就会到最后的结果小于循环的次数
    #这是因为当不同的线程同时操作var+1 造成的,例如当t1,t2同时读到的var为10,他们分别加1那最后的结果是11,而不是我们想要的12

    for i in range(5000):
        t = incrementThread()
        threads.append(t)
        t.start()

    for t in threads:
        t.join()

    print "After 5000 modifications, var is %d" % (var)


if __name__ == '__main__':
    use_increment_thread()


执行结果:省略一大部分
[root@teach threading]$ python t2.py
var in Thread-4998 after increment is 4901
var in Thread-4999 is 4901
var in Thread-4999 after increment is 4902
var in Thread-5000 is 4902
var in Thread-5000 after increment is 4903
After 5000 modifications, var is 4903

意思是产生了5000个子线程,但是var只累加到了4903。

这就是多线程是无序的。var=10的时候,可能产生了2个子线程同时对一个var累加,那么的出来的var=11,而不是var=12

8:线程锁

接下来看看加了进程锁的例子:

#!/usr/bin/env python
#-*- coding:utf-8 -*-


from threading import Lock, Thread

#生成锁对象
lock = Lock()

some_var = 0


class IncrementThread(Thread):

    def run(self):
        global some_var
        #申请锁
        lock.acquire()
        read_value = some_var
        print "some_var in %s is %d" % (self.name, read_value)
        some_var = read_value + 1
        print "some_var in %s after increment is %d" % (self.name, some_var)
        #释放锁
        lock.release()

  

def use_increment_thread():

    threads = []
    for i in range(5000):
        t = IncrementThread()
        threads.append(t)
        t.start()

    for t in threads:
        t.join()

    print "After 5000 modifications, some_var should have become 5000"
    print "After 5000 modifications, some_var is %d" % (some_var,)

  

use_increment_thread()


执行结果:只截取最后面一部分
[root@teach threading]$ python t2.py
some_var in Thread-4998 after increment is 4998
some_var in Thread-4999 is 4998
some_var in Thread-4999 after increment is 4999
some_var in Thread-5000 is 4999
some_var in Thread-5000 after increment is 5000
After 5000 modifications, some_var should have become 5000
After 5000 modifications, some_var is 5000

这就是线程锁的效果了,产生了5000个子线程,var也累加了5000次。

9:线程锁,函数写法

#!/usr/bin/env python
#-*- coding:utf-8 -*-

from threading import Lock, Thread

# 生成锁对象
lock = Lock()

some_var = 0

def run(num):
    global some_var
    # 申请锁
    lock.acquire()
    read_value = some_var
    print("some_var in %s is %d" % (num, read_value))
    some_var = read_value + 1
    print("some_var in %s after increment is %d" % (num, some_var))
    # 释放锁
    lock.release()

def use_increment_thread():
    threads = []

    for i in range(5000):
        t = Thread(target=run, args=(i,))
        threads.append(t)
        t.start()

    for t in threads:
        t.join()

    print("After 5000 modifications, some_var should have become 5000")
    print("After 5000 modifications, some_var is %d" % (some_var,))



if __name__ == "__main__":
    use_increment_thread()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值