精华内容
下载资源
问答
  • Python作为目前跨平台应用最火编程语言,已经很多行业研究人员所使用,其实与其他编程语言相同,Python也只支持多线程编程,而且Python对于线程支持也提供了很多方便方法可以直接调用,从而可以很好控制...

    Python作为目前跨平台应用最火的编程语言,已经被很多行业的研究人员所使用,其实与其他编程语言相同,Python也只支持多线程编程,而且Python对于线程的支持也提供了很多方便的方法可以直接调用,从而可以很好的控制线程的执行。

    下面咱们就来聊聊Python对于线程所提供的join()方法吧。

    join()方法的功能是可以让一个线程等待另一个线程的完成,是Thread模块所提供的,例如当在一个程序执行流中调用其他线程的join()方法时,调用线程就会被阻塞,一直到被join()方法所加入的join线程执行完成。

    其实呢,join()方法通常由使用线程的程序调用,可以理解为将一个大问题划分为很多小问题,然后为每个小问题分配一个线程,当这些小问题都执行处理完毕后,才会回到主程序进一步进行接下来的操作。

    为了大家能够比较清晰的理解,咱们来看一下“栗子”吧:

    上面咱们所写的程序中,可以看到一共有三个线程,在咱们所写的主程序中开始位置就启动了咱们所命名为“新线程”的子线程,此时这个线程就会和主线程进行并发执行,然后当主线程的循环变量i等于5时,就会启动咱们所命名为“被Join的线程”的线程,这个线程并不会和主线程并发执行,主线程必须等到这个线程执行结束后才会向下执行,所以在“被Join的线程”的线程执行时,实际上是只有两个子线程并发执行的,分别是“新线程”线程和“被Join的线程”线程,而对于主线程此时则是处于等待状态,这就是join()方法的作用。

    好啦,下面咱们就调用Python解释器来看一下上面咱们所编写的程序的效果吧:

    运行上面程序,就会输出如下三个线程的并发执行情况:

    观察上面的输出可以看到,主线程执行到i==5时,程序启动并join了名为“被Join的线程”的线程,所以主线程将一直处于阻塞状态,一直到名为“被Join的线程”的线程执行完成才会跳到主线程“MainThread”中。

    最后咱们再来看一下Thread的join()方法的参数吧,其实join()是有参数的,join的语法结构为join(timeout=None),可以看到join()方法有一个timeout参数,其默认值为None,而参数timeout可以进行赋值,其含义是指定等待被join的线程的时间最长为timeout秒,也就是说当在timeout秒内被join的线程还没有执行结束的话,就不再进行等待了。

    好啦,以上就是今天咱们所聊的关于Python控制线程中的join线程的相关知识,希望大家能够学会join()方法的调用,今天就聊到这吧,下次再见哦!

    Python线程的生命周期你知道多少,一文帮你全部搞清楚

    Python并发编程很简单,一文帮你搞清如何创建线程类

    敬请关注“品位集结号”,为您带来意外的小知识!

    展开全文
  • 假设现在是 2008-4-7 12:00:00.000,如果我调用一下 Thread.Sleep(1000) ,在 2008-4-7 12:00:01.000 时候,这个线程会 不会唤醒? 某人代码中用了一句看似莫明其妙话:Thread.Sleep(0) 。...

    我们可能经常会用到 Thread.Sleep 函数来使线程挂起一段时间。那么你有没有正确的理解这个函数的用法呢?思考下面这两个问题:

    假设现在是 2008-4-7 12:00:00.000,如果我调用一下 Thread.Sleep(1000) ,在 2008-4-7 12:00:01.000 的时候,这个线程会 不会被唤醒?
    某人的代码中用了一句看似莫明其妙的话:Thread.Sleep(0) 。既然是 Sleep 0 毫秒,那么他跟去掉这句代码相比,有啥区别么?
    我们先回顾一下操作系统原理。

    操作系统中,CPU竞争有很多种策略。Unix系统使用的是时间片算法,而Windows则属于抢占式的。

    在时间片算法中,所有的进程排成一个队列。操作系统按照他们的顺序,给每个进程分配一段时间,即该进程允许运行的时间。如果在 时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。调度程 序所要做的就是维护一张就绪进程列表,,当进程用完它的时间片后,它被移到队列的末尾。

    所谓抢占式操作系统,就是说如果一个进程得到了 CPU 时间,除非它自己放弃使用 CPU ,否则将完全霸占 CPU 。因此可以看出,在抢 占式操作系统中,操作系统假设所有的进程都是“人品很好”的,会主动退出 CPU 。

    在抢占式操作系统中,假设有若干进程,操作系统会根据他们的优先级、饥饿时间(已经多长时间没有使用过 CPU 了),给他们算出一 个总的优先级来。操作系统就会把 CPU 交给总优先级最高的这个进程。当进程执行完毕或者自己主动挂起后,操作系统就会重新计算一 次所有进程的总优先级,然后再挑一个优先级最高的把 CPU 控制权交给他。

    我们用分蛋糕的场景来描述这两种算法。假设有源源不断的蛋糕(源源不断的时间),一副刀叉(一个CPU),10个等待吃蛋糕的人(10 个进程)。

    如果是 Unix操作系统来负责分蛋糕,那么他会这样定规矩:每个人上来吃 1 分钟,时间到了换下一个。最后一个人吃完了就再从头开始。于是,不管这10个人是不是优先级不同、饥饿程度不同、饭量不同,每个人上来的时候都可以吃 1 分钟。当然,如果有人本来不太饿,或者饭量小,吃了30秒钟之后就吃饱了,那么他可以跟操作系统说:我已经吃饱了(挂起)。于是操作系统就会让下一个人接着来。

    如果是 Windows 操作系统来负责分蛋糕的,那么场面就很有意思了。他会这样定规矩:我会根据你们的优先级、饥饿程度去给你们每个人计算一个优先级。优先级最高的那个人,可以上来吃蛋糕——吃到你不想吃为止。等这个人吃完了,我再重新根据优先级、饥饿程度来计算每个人的优先级,然后再分给优先级最高的那个人。

    这样看来,这个场面就有意思了——可能有些人是PPMM,因此具有高优先级,于是她就可以经常来吃蛋糕。可能另外一个人是个丑男,而且很ws,所以优先级特别低,于是好半天了才轮到他一次(因为随着时间的推移,他会越来越饥饿,因此算出来的总优先级就会越来越高,因此总有一天会轮到他的)。而且,如果一不小心让一个大胖子得到了刀叉,因为他饭量大,可能他会霸占着蛋糕连续吃很久很久,导致旁边的人在那里咽口水。。。
    而且,还可能会有这种情况出现:操作系统现在计算出来的结果,5号PPMM总优先级最高,而且高出别人一大截。因此就叫5号来吃蛋糕。5号吃了一小会儿,觉得没那么饿了,于是说“我不吃了”(挂起)。因此操作系统就会重新计算所有人的优先级。因为5号刚刚吃过,因此她的饥饿程度变小了,于是总优先级变小了;而其他人因为多等了一会儿,饥饿程度都变大了,所以总优先级也变大了。不过这时候仍然有可能5号的优先级比别的都高,只不过现在只比其他的高一点点——但她仍然是总优先级最高的啊。因此操作系统就会说:5号mm上来吃蛋糕……(5号mm心里郁闷,这不刚吃过嘛……人家要减肥……谁叫你长那么漂亮,获得了那么高的优先级)。

    那么,Thread.Sleep 函数是干吗的呢?还用刚才的分蛋糕的场景来描述。上面的场景里面,5号MM在吃了一次蛋糕之后,觉得已经有8分饱了,她觉得在未来的半个小时之内都不想再来吃蛋糕了,那么她就会跟操作系统说:在未来的半个小时之内不要再叫我上来吃蛋糕了。这样,操作系统在随后的半个小时里面重新计算所有人总优先级的时候,就会忽略5号mm。Sleep函数就是干这事的,他告诉操作系统“在未来的多少毫秒内我不参与CPU竞争”。

    sleep==> 将当前线程挂起指定的时间

    看完了 Thread.Sleep 的作用,我们再来想想文章开头的两个问题。

    对于第一个问题,答案是:不一定。因为你只是告诉操作系统:在未来的1000毫秒内我不想再参与到CPU竞争。那么1000毫秒过去之后,这时候也许另外一个线程正在使用CPU,那么这时候操作系统是不会重新分配CPU的,直到那个线程挂起或结束;况且,即使这个时候恰巧轮到操作系统进行CPU 分配,那么当前线程也不一定就是总优先级最高的那个,CPU还是可能被其他线程抢占去。

    与此相似的,Thread有个Resume函数,是用来唤醒挂起的线程的。好像上面所说的一样,这个函数只是“告诉操作系统我从现在起开始参与CPU竞争了”,这个函数的调用并不能马上使得这个线程获得CPU控制权。

    对于第二个问题,答案是:有,而且区别很明显。假设我们刚才的分蛋糕场景里面,有另外一个PPMM 7号,她的优先级也非常非常高(因为非常非常漂亮),所以操作系统总是会叫道她来吃蛋糕。而且,7号也非常喜欢吃蛋糕,而且饭量也很大。不过,7号人品很好,她很善良,她没吃几口就会想:如果现在有别人比我更需要吃蛋糕,那么我就让给他。因此,她可以每吃几口就跟操作系统说:我们来重新计算一下所有人的总优先级吧。不过,操作系统不接受这个建议——因为操作系统不提供这个接口。于是7号mm就换了个说法:“在未来的0毫秒之内不要再叫我上来吃蛋糕了”。这个指令操作系统是接受的,于是此时操作系统就会重新计算大家的总优先级——注意这个时候是连7号一起计算的,因为“0毫秒已经过去了”嘛。因此如果没有比7号更需要吃蛋糕的人出现,那么下一次7号还是会被叫上来吃蛋糕。

    因此,Thread.Sleep(0)的作用,就是“触发操作系统立刻重新进行一次CPU竞争”。竞争的结果也许是当前线程仍然获得CPU控制权,也许会换成别的线程获得CPU控制权。这也是我们在大循环里面经常会写一句Thread.Sleep(0) ,因为这样就给了其他线程比如Paint线程获得CPU控制权的权力,这样界面就不会假死在那里。

    末了说明一下,虽然上面提到说“除非它自己放弃使用 CPU ,否则将完全霸占 CPU”,但这个行为仍然是受到制约的——操作系统会监控你霸占CPU的情况,如果发现某个线程长时间霸占CPU,会强制使这个线程挂起,因此在实际上不会出现“一个线程一直霸占着 CPU 不放”的情况。至于我们的大循环造成程序假死,并不是因为这个线程一直在霸占着CPU。实际上在这段时间操作系统已经进行过多次CPU竞争了,只不过其他线程在获得CPU控制权之后很短时间内马上就退出了,于是就又轮到了这个线程继续执行循环,于是就又用了很久才被操作系统强制挂起。。。因此反应到界面上,看起来就好像这个线程一直在霸占着CPU一样。

    展开全文
  • 函数不同的控制流程调用,有时在之前的函数正在访问函数变量的时候,线程切出去执行别的流程再次在别的线程放访问当前的函数,这个过程叫做是重入。  详细说的话, 假设当前的程序是多线程的程序 ,当...

    今天,学到了两个概念。线程安全、与可重入函数 这个都是关于多线程的,下面我们来看看这两个概念到底有什么不同之处。

    先来看看这两个概念

    1、什么是可重入函数

    函数被不同的控制流程调用,有时会在之前的函数正在访问函数变量的时候,线程被切出去执行别的流程再次在别的线程放访问当前的函数,这个过程叫做是重入。
     详细说的话,

    假设当前的程序是多线程的程序 ,当程序运行到某一个函数的时候,可能因为硬件中断或者异常而使得在用户正在执行的代码暂时终端转而进入你内核,这个时候如有一个信号需要被处理,而处理的这个信号的时候又会重新调用刚才中断的函数,如果函数内部有一个全局变量需要被操作,那么,当信号处理完成之后重新返回用户态恢复中断函数的上下文再次继续执行的时候,对同一个全局变量的操作结果可能就会发生改变而并不如我们预期的那样,这样的函数被称为不可重入函数。例如在进行链表的的插入时,插入函数访问一个全局链表,有可能因为重入而造成错乱。

    相对应的,当一个执行流因为异常或者被内核切换而中断正在执行的函数而转为另外一个执行流时,当后者的执行流对同一个函数的操作并不影响前一个执行流恢复后执行函数产生的结果,我们就称这个函数为可重入函数

    可重入函数只访问自己的局部变量或参数

    其实总结就是:一个可重入函数可以被多个执行流重复进入,内部使用的数据都应该来自于自身的栈空间,包括返回值也不应该是全局或者静态的,可以允许有该函数的多个副本在运行,而正是因为其中的操作数据都来自于自身的栈空间,而每次调用函数会开辟不同的栈空间,因此二者互不影响。

    2、什么是线程安全

     当多个线程类并发操作某类的某个方法,(在该方法内部)来修改这个类的某个成员变量的值,不会出错,则我们就说,该的这个方法是线程安全的。

      某类的某方法是否线程安全的关键是:

      (1) 该方法是否修改该类的成员变量;

      (2) 是否给该方法加锁(是否用synchronized关键字修饰)

    所以,有这么四类函数称为线程不安全的

    1、不保护共享变量的函数;
    2、函数状态随着调用改变的函数;
    3、返回指向静态变量指针的函数;
    4、调用线程不安全函数的函数;

    如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码如果每次运行结果和运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。或者说一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
            线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
    一般来说,一个函数被称为线程安全的,当且仅当被多个并发线程反复调用时,它会一直产生正确的结果

    3、可重入函数的分类

    (1)显式可重入函数
           如果所有函数的参数都是传值传递的(没有指针),并且所有的数据引用都是本地的自动栈变量(也就是说没有引用静态或全局变量),那么函数就是显示可重入的,也就是说不管如何调用,我们都可断言它是可重入的。
    (2)隐式可重入函数
           可重入函数中的一些参数是引用传递(使用了指针),也就是说,在调用线程小心地传递指向非共享数据的指针时,它才是可重入的。
           可重入函数可以有多余一个任务并发使用,而不必担心数据错误,相反,不可重入函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在 代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据,可重入函数要么使用本地变量,要么在使用全局变量时保护自己 的数据。

    一个可重入函数需要满足的是:

    1、不使用全局变量或静态变量;
    2、不使用用malloc或者new开辟出的空间;
    3、不调用不可重入函数;
    4、不返回静态或全局数据,所有数据都有函数的调用者提供;
    5、使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据;

    不可重入函数符合以下条件之一:
    1、调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的。
    2、调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
    3、可重入体内使用了静态的数据结构。

    4、线程安全与可重入函数的区别

    (1)、可重入函数是线程安全函数的一种,其特点在于它们被多个线程调用时,不会引用任何共享数据。
    (2)、线程安全是在多个线程情况下引发的,而可重入函数可以在只有一个线程的情况下来说。
    (3)、线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
    (4)、如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。
    (5).如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的。
    (6)、线程安全函数能够使不同的线程访问同一块地址空间,而可重入函数要求不同的执行流对数据的操作互不影响使结果是相同的。

    展开全文
  • Pythonthreading多线程

    2020-01-06 23:31:45
    Python创建线程的两种...另外一种是单独编写函数func, 并使用threading.Thread(target=func), 该方法会被run()调用下面我们使用线程实现打印一个从begin到end整数需求。 继承Thread类创建线程 import thre...

    Python创建线程的两种方式

    Python有两种方式创建线程:
    一种是创建一个新的类并继承threading.Thread类, 然后重写run()方法;
    另外一种是单独编写函数func, 并使用threading.Thread(target=func), 该方法会被run()调用。

    下面我们使用线程实现打印一个从begin到end的整数的需求。

    继承Thread类创建线程

    import threading
    import time
    
    
    class ThreadDemo(threading.Thread):
        def __init__(self, begin, end):
            super().__init__()
            self.begin = begin
            self.end = end
    
        def run(self):
            """
            重写run()方法
            """
            for i in range(self.begin, self.end):
                print(i)
               
    thread_demo = ThreadDemo(1, 999)
    thread_demo.start()
    

    使用target调用

    import threading
    
    def print_range(start, end):
        for i in range(start, end):
            print(i)
    
    thread_func = threading.Thread(target=print_range, args=(1, 10))
    thread_func.start()
    



    线程的方法

    start

    start() 方法用来启动一个线程。
    它在一个线程里最多只可被调用一次, 调用它时会执行run()函数。

    run

    线程执行的具体方法。
    你可以在子类里重写这个方法; 如果使用target传入函数名称以及参数,也会调用并执行这个方法。

    真正调起线程的是start方法, 而run方法只是一个普通函数。
    使用上面的例子稍加改造验证一下:

    首先使用start方法:

    import threading
    import time
    
    
    def print_range(start, end):
        for i in range(start, end):
            print(i, end=" ")
            time.sleep(0.1)
    
    
    func1 = threading.Thread(target=print_range, args=(1, 10))
    func2 = threading.Thread(target=print_range, args=(1, 10))
    func1.start()
    func2.start()
    

    输出

    1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 
    

    可以看到线程是并发执行。

    使用run方法:

    import threading
    import time
    
    
    def print_range(start, end):
        for i in range(start, end):
            print(i, end=" ")
            time.sleep(0.1)
    
    
    func1 = threading.Thread(target=print_range, args=(1, 10))
    func2 = threading.Thread(target=print_range, args=(1, 10))
    func1.run()
    func2.run()
    

    输出

    1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 
    

    可以看出, 是按照顺序执行的, 并不是并发执行。

    join

    阻塞本线程, 直到线程结束或者timeout超时

    import threading
    import time
    
    
    def print_range(start, end):
        for i in range(start, end):
            print("{} {}".format(threading.current_thread().name, i))
            print("{} {}".format(threading.current_thread().name, threading.current_thread().is_alive()))
            time.sleep(0.1)
    
    
    func1 = threading.Thread(target=print_range, args=(1, 5))
    func2 = threading.Thread(target=print_range, args=(1, 5))
    func1.start()
    func1.join()
    print(func1.is_alive())
    func2.start()
    

    输出

    Thread-1 1
    Thread-1 True
    Thread-1 2
    Thread-1 True
    Thread-1 3
    Thread-1 True
    Thread-1 4
    Thread-1 True
    False		# 线程1阻塞直到结束
    Thread-2 1
    Thread-2 True
    Thread-2 2
    Thread-2 True
    Thread-2 3
    Thread-2 True
    Thread-2 4
    Thread-2 True
    

    daemon

    用于创建守护线程, 当所有的非守护线程都结束时, 守护线程会自动结束, 整个Python程序也会结束。

    设置线程1为守护线程, 当非守护线程2结束时, 整个程序都会结束

    import threading
    import time
    
    
    def print_range(start, end):
        for i in range(start, end):
            print("{} {}".format(threading.current_thread().name, i))
            time.sleep(0.1)
    
    
    func1 = threading.Thread(target=print_range, args=(1, 5000), daemon=True)
    func2 = threading.Thread(target=print_range, args=(1, 5))
    func1.start()
    func2.start()
    

    输出

    Thread-1 1
    Thread-2 1
    Thread-1 2
    Thread-2 2
    Thread-2 3
    Thread-1 3
    Thread-2 4
    Thread-1 4 		# 非守护线程结束
    Thread-1 5		# 守护线程也随之结束, 程序结束
    

    线程锁

    多线程运行时, 所有线程共享内存, 需要操作全局变量时, 多个线程会竞争资源, 导致全局变量处于一个不可预测的状态。

    引用廖雪峰老师的例子, 存取的数量相等, 无论循环多少次,最终的结果应该是0

    import threading
    balance = 0
    
    def change_it(n):
        # 先存后取,结果应该为0:
        global balance
        balance = balance + n
        balance = balance - n
    
    def run_thread(n):
        for i in range(1000000):
            change_it(n)
    
    t1 = threading.Thread(target=run_thread, args=(5,))
    t2 = threading.Thread(target=run_thread, args=(8,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(balance)
    

    加大一个数量级的循环后,最终的balance是6。

    如果没有线程锁的存在, 一个线程读的时候正好另外一个线程写, 就会导致余额不正确。

    • acquire() 会使线程处于lock状态
    • release() 会释放锁
    • acquire() 和 release() 在一个线程中总是成对出现的

    Lock的状态转换

    锁的好处:

    • 保证了一段代码在一个线程中完整的执行

    坏处:

    • 降低了多线程运行的效率
    • 多个线程存在多个锁的情况下,线程之间在试图获取对方的锁时,有可能导致程序死锁, 死锁后线程都会挂起, 只能强制杀死程序。

    使用with代替acquire()和release()

    acquire() 和 release() 可以被用作with的上下文管理器:当进入语句时acquire()被调用, 退出语句时release()被调用。
    以下语句:

    with some_lock:
    	do_something()
    

    等同于

    some_lock.acquire()
    try:
    	do_something()
    finally:
    	some_lock.release()
    

    线程之间的通信

    wait()、notify()和notify_all() 方法用于线程间的通信

    • wait(timeout=None)
      释放锁, 然后阻塞线程, 直至其他线程的notify()和notify_all()唤起该线程。 一旦被唤起, 它会重新获取锁并返回。 它可以设置超时时间
    • notify(n=1)
      唤起一个或者多个处于waiting状态的线程, 线程的唤起是随机的。
      注意: 不会释放锁。
    • notify_all()
      唤起所有处于waiting状态的线程。
      注意: 不会释放锁。

    生产者-消费者模型

    线程间通信的典型应用是生产者-消费者模型。

    生产者生产商品, 当生产到最大库存的时候通知消费者消费;消费者消费产品, 当库存为0的时候通知生产者生产。
    实现代码如下:

    import threading
    import random
    import time
    
    
    class Producer(threading.Thread):
        def run(self):
            class_name = __class__.__name__
            global max_amount
            global products
            while True:
                # 获得锁
                condition.acquire()
    
                # 继续生产
                if len(products) < max_amount:
                    product = random.randint(1, 100)
                    print("{} produce {}".format(class_name, product))
                    products.append(product)
    
                else:
                    # 生产完毕, 通知消费者
                    print("{} notify Consumer".format(class_name))
                    condition.notify()
                    condition.wait()
    
                print("{} unlock".format(class_name))
                condition.release()
                time.sleep(1)
    
    
    class Consumer(threading.Thread):
        def run(self):
            class_name = __class__.__name__
            global max_amount
            global products
            while True:
                # 获得锁
                condition.acquire()
    
                # 继续消费
                if len(products) > 0:
                    consume = products.pop(0)
                    print("{} consume {}".format(class_name, consume))
    
                else:
                    # 消费完毕, 通知生产者
                    print("{} notify Producer".format(class_name))
                    condition.notify()
                    condition.wait()
    
                print("{} unlock".format(class_name))
                condition.release()
                time.sleep(1)
    
    
    products = []
    max_amount = 5
    
    condition = threading.Condition()
    producer = Producer()
    consumer = Consumer()
    producer.start()
    consumer.start()
    

    改造一下生产者-消费者模型, 实现生产者每生产1个商品, 消费者便消费掉。只需在生产者生产后调用wait(), 生产者线程阻塞, 释放锁;消费者消费掉产品,调用notify()通知生产者继续生产。
    代码如下:

    import threading
    import random
    import time
    
    
    class Producer(threading.Thread):
        def run(self):
            class_name = __class__.__name__
            global max_amount
            global products
            while True:
                # 获得锁
                condition.acquire()
    
                # 继续生产
                while len(products) < max_amount:
                    product = random.randint(1, 100)
                    print("{} produce {}".format(class_name, product))
                    products.append(product)
                    condition.wait()
    
                # 生产完毕, 通知消费者
                # print("{} notify Consumer".format(class_name))
                # condition.notify()
                #
                # print("{} unlock".format(class_name))
                # condition.release()
                # time.sleep(1)
    
    
    class Consumer(threading.Thread):
        def run(self):
            class_name = __class__.__name__
            global max_amount
            global products
            while True:
                # 获得锁
                condition.acquire()
    
                # 继续消费
                while len(products) > 0:
                    consume = products.pop(0)
                    print("{} consume {}".format(class_name, consume))
                    # condition.wait()
    
                # 消费完毕, 通知生产者
                print("{} notify Producer".format(class_name))
                condition.notify()
    
                print("{} unlock".format(class_name))
                condition.release()
                time.sleep(1)
    
    
    products = []
    max_amount = 5
    condition = threading.Condition()
    producer = Producer()
    consumer = Consumer()
    producer.start()
    consumer.start()
    

    交替打印奇偶数

    给定一个数字范围, 使用多线程交替打印奇偶数。注意循环退出的位置。

    import threading
    
    
    class Even(threading.Thread):
        def __init__(self, range_num):
            super().__init__()
            self.range_num = range_num
    
        def run(self):
            global num
            condition.acquire()
            while True:
                if num % 2 == 0:
                    if num == self.range_num:
                        break
                    print("{} {}".format(__class__.__name__, num))
                    num += 1
                else:
                    condition.notify()
                    # 循环退出必须在notify后, 否则另外一个线程会一直处于wait状态
                    if num == self.range_num:
                        break
                    condition.wait()
    
            condition.release()
    
    
    class Odd(threading.Thread):
        def __init__(self, range_num):
            super().__init__()
            self.range_num = range_num
    
        def run(self):
            global num
            condition.acquire()
            while True:
                if num % 2 == 1:
                    if num == self.range_num:
                        break
                    print("{} {}".format(__class__.__name__, num))
                    num += 1
                else:
                    condition.notify()
                    # 循环退出必须在notify后, 否则另外一个线程会一直处于wait状态
                    if num == self.range_num:
                        break
                    condition.wait()
    
            condition.release()
    
    
    condition = threading.Condition()
    num = 0
    
    even_thread = Even(10)
    odd_thread = Odd(10)
    even_thread.start()
    odd_thread.start()
    even_thread.join()
    odd_thread.join()
    



    线程安全的数据类型

    线程安全:多线程环境中,共享数据同一时间只能有一个线程来操作。
    原子操作:原子操作就是不会因为进程并发或者线程并发而导致被中断的操作

    当对全局资源存在写操作时,如果不能保证写入过程的原子性,会出现脏读脏写的情况,即线程不安全。Python的GIL只能保证原子操作的线程安全,因此在多线程编程时我们需要通过加锁来保证线程安全。上面我们用互斥锁实现了线程安全, 在Python中有些数据类型本身就是线程安全的, 比如queue.Queue和collections.deque。

    使用Queue编写一个生产者-消费者模型:

    from threading import Thread, Condition
    import time
    import random
    from queue import Queue
    
    queue = Queue(10)
    
    
    class ProducerThread(Thread):
        def run(self):
            nums = range(5)
            global queue
            while True:
                num = random.choice(nums)
                queue.put(num)
                print("Produce: {}".format(num))
                time.sleep(random.random())
    
    
    class ConsumerThread(Thread):
        def run(self):
            global queue
            while True:
                num = queue.get()
                queue.task_done()
                print("Consume: {}".format(num))
                time.sleep(random.random())
    
    ProducerThread().start()
    ConsumerThread().start()
    

    Timer类

    指定n秒后执行操作

    from threading import Timer
    
    def hello():
        print("hello, world")
    
    t = Timer(10, hello)
    t.start()  # after 10 seconds, "hello, world" will be printed
    



    GIL 全局解释器锁

    GIL的来源

    由于物理上得限制,各CPU厂商在核心频率上的比赛已经被多核所取代。为了更有效的利用多核处理器的性能,就出现了多线程的编程方式,而随之带来的就是线程间数据一致性和状态同步的困难。即使在CPU内部的Cache也不例外,为了有效解决多份缓存之间的数据同步时各厂商花费了不少心思,也不可避免的带来了一定的性能损失。

    Python当然也逃不开,为了利用多核,Python开始支持多线程。而解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁。 于是有了GIL这把超级大锁,而当越来越多的代码库开发者接受了这种设定后,他们开始大量依赖这种特性(即默认python内部对象是thread-safe的,无需在实现时考虑额外的内存锁和同步操作)。

    GIL的缺陷

    就像python解释器中一把大锁,能够保证线程对数据的大部分常规修改操作(类原子操作:也就是不可分割,如append,pop)的安全,python全局解释器锁有一个特点,执行15毫秒后的操作系统cpu轮转机制(也就是定期释放锁),也就是说某一个线程在获得GIL锁后被执行15ms,会自动释放GIL锁,GIL被释放后该线程又会进入就绪列队,等待运行,这个时候其他线程可以获取GIL锁,从而获取CPU资源执行任务,下一次下回来的时候继续从第一个线程之前离开的位置继续执行,这种模式保证了多线程之间的同时被执行,不会导致一段时间了只有一个线程被执行知道执行完,这明显不合理。

    来看一下单线程和多线程的执行效率对比, 我们用两个单线程进行1亿的计数, 和用两个多线程并发计数, 看一下执行效率:

    from threading import Thread
    import time
    
    
    def my_counter():
        i = 0
        for _ in range(100000000):
            i = i + 1
        return True
    
    
    def main():
        start_time = time.time()
        for tid in range(2):
            t = Thread(target=my_counter)
            t.start()
            t.join()
        end_time = time.time()
        print("Total time: {}".format(end_time - start_time))
    
    
    if __name__ == '__main__':
        main()
    

    运行结果: Total time: 9.658979654312134

    from threading import Thread
    import time
    
    
    def my_counter():
        i = 0
        for _ in range(100000000):
            i = i + 1
        return True
    
    
    def main():
        thread_array = {}
        start_time = time.time()
        for tid in range(2):
            t = Thread(target=my_counter)
            t.start()
            thread_array[tid] = t
        for i in range(2):
            thread_array[tid].join()
        end_time = time.time()
        print("Total time: {}".format(end_time - start_time))
    
    
    if __name__ == '__main__':
        main()
    

    运行结果:Total time: 11.66282033920288

    由于GIL锁的存在, 多线程的并发效率比单线程还要差一些, 但是Python3.6比Python2.7的并发效率要好一些。

    所以Python一般用multiprocess多进程来代替多线程。

    参考

    • https://www.cnblogs.com/ryxiong-blog/p/10730085.html
    • https://www.liaoxuefeng.com/wiki/1016959663602400/1017629247922688
    • https://www.cnblogs.com/SuKiWX/p/8804974.html
    • https://www.cnblogs.com/ArsenalfanInECNU/p/10022740.html
    展开全文
  • c++多线程传递引用参数问题

    千次阅读 2020-03-25 21:48:42
    多线程之间传递参数时,不能简单地把线程创建当作函数调用,在进行子函数调用时,父函数的局部变量不会被销毁,而在创建线程时,若没有join该线程,那么局部变量可能会被销毁,致使引用或指针失效。 下面的代码是...
  • 多线程一道笔试题

    2018-08-26 19:34:59
    假设下面的函数foo会被多线程调用,那么函数里面使用的变量i,j,k哪些因为线程间共享访问需要加密保护()?     目前多线程编程接触的少,这道题不会了,做个笔记。 线程有自己的栈,局部变量存储在栈中...
  • 新创建的线程会将将id填充到thread指针内存线程属性:调度策略/继承/分离(这里暂时不用理会,调用时候,传入参数NULL表示使用默认属性)回调函数的指针:当前线程栈通过传入函数指针调用被调用函数的主体回...
  • Qt之多线程简单学习

    2017-06-20 21:14:15
    run()是个纯虚函数,是线程执行入口,在run()里出现代码将在另外线程执行。run()函数是通过start()函数来实现调用的下面是我学习时例子:工程文件: 界面效果: 运行效果: 下面贴出代码,大家...
  • 思考下面这两个问题:假设现在是 2008-4-7 12:00:00.000,如果我调用一下 Thread.Sleep(1000) ,在 2008-4-7 12:00:01.000 时候,这个线程会 不会唤醒?某人代码中用了一句看似莫明其妙话:Thread.Sleep(0) ...
  • 我们可能经常用到 ...假设现在是 2008-4-7 12:00:00.000,如果我调用一下 Thread.Sleep(1000) ,在 2008-4-7 12:00:01.000 时候,这个线程会 不会唤醒? 某人代码中用了一句看似莫明其妙话:Thread.Sleep
  • 下面梳理多线程、创建窗口、消息循环方式和结果: 一、以往大多在WIN32界面程序创建窗口,即: int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdSh
  • 看到这样一篇文章:Java多线程 -sleep 用法详解 ...假设现在是 2008-4-7 12:00:00.000,如果我调用一下 Thread.Sleep(1000) ,在 2008-4-7 12:00:01.000 时候,这个线程 不会唤醒? 某人代码中用...
  • 目录 1.C语言本身没有输入输出语句; C语言中的关键字: ...2.关于C++线程安全: ...线程兼容 ...线程对立 ...下面哪个阶段可以发现被调用的函数未定义? 4.char *p="girl"; 5.类的成员有三种访问属性,...
  • python中线程的使用

    2013-06-05 22:08:12
    线程启动之后,执行一个函数,当该函数执行完之后,线程也就结束了...当线程调用函数时,则该函数陷入了死循环。当满足某种条件之后,可以通过break退出该循环。本blog中介绍“双线程高效下载问题”和“线
  • Python中线程的使用

    千次阅读 2012-07-30 20:48:00
    线程启动之后,执行一个函数,当该函数执行完之后,线程也就结束了...当线程调用函数时,则该函数陷入了死循环。当满足某种条件之后,可以通过break退出该循环。本blog中介绍“双线程高效下载问题”和“线
  • Java多线程 -sleep 用法详解(转)

    千次阅读 2019-06-02 16:26:23
    假设现在是 2008-4-7 12:00:00.000,如果我调用一下 Thread.Sleep(1000) ,在 2008-4-7 12:00:01.000 时候,这个线程会 不会唤醒? 某人代码中用了一句看似莫明其妙话:Thread.Sleep(0)...
  • 思考下面这两个问题:假设现在是 2008-4-7 12:00:00.000,如果我调用一下 Thread.Sleep(1000) ,在 2008-4-7 12:00:01.000 时候,这个线程会 不会唤醒?某人代码中用了一句看似莫明其妙...
  • pthread线程

    2013-06-19 11:58:05
    pthread是linux下面遵循posix标准的多线程接口,该线程测试,就类似于RTOS任务测试是一样,接下来主要测试了其中两个函数pthread_create线程创建和pthread_join等待线程结束  如果主线程中不调用pthread_...
  • 东阳学习笔记 审慎使用 fork() fork()可能会导致资源泄漏 但是,假如程序会fork(),上面假设就会... //析构函数会被调用两次,父进程和子进程各一次 } 还可能会出现错误:如果Foo class封装了某种资源,而.
  • v4.7(2019-05-23)修正第二次创建代理提取架构后无法获取到代理的问题增加了两个新的函数`获取代理Ex()` `自压入代理Ex()` '//详情请查看`例程2.e`增加了两个例程`例程1.e` `例程2.e`使用方法:1. (编辑配置)1.1 ...
  • Event对象  Event对象是一个同步对象,它状态可以SetEvent函数显式设置成为...Event对象状态保持signaled状态,除非显式的调用ReseEvent函数将它重置为nonsignaled状态。一旦它状态设置成为sign
  • 为了解决这几个个长期困扰我问题,以及未来经常遇到其它问题(例如AES加解密、验证码图片处理、文本编解码、大数数学运算、多线程等等),我考虑在VBA中调用C#dll来彻底解决。 正常情况下,vba调用C#...
  • 《Thinking in JAVA》中有这么一句话,当notify()函数因为某个特定锁被调用时,只有等待这个锁的任务才会被唤醒。什么意思?看下面的代码,这个代码执行的话会报错,java.lang.IllegalMonitorStateException上网查了...
  • Java多线程系列更新中~正式篇:番外...思考下面这两个问题:假设现在是 2008-4-7 12:00:00.000,如果我调用一下 Thread.Sleep(1000) ,在 2008-4-7 12:00:01.000 时候,这个线程 不会唤醒?某人代码中用了一...
  • 谈谈函数式编程

    2020-12-31 02:38:23
    便是笔者在项目实践中应用较多的函数式编程内容,如有不妥,请斧正。 附: 一些可供学习函数式编程的内容 - Immutable.js (https://facebook.github.io/immutable-js/) - Underscore ...
  • Java多线程系列更新中~正式篇:番外...思考下面这两个问题:假设现在是 2008-4-7 12:00:00.000,如果我调用一下 Thread.Sleep(1000) ,在 2008-4-7 12:00:01.000 时候,这个线程 不会唤醒?某人代码中用了一...
  • Java多线程系列更新中~正式篇:番外...思考下面这两个问题:假设现在是 2008-4-7 12:00:00.000,如果我调用一下 Thread.Sleep(1000) ,在 2008-4-7 12:00:01.000 时候,这个线程 不会唤醒?某人代码中用了一...
  • 这时安装程序(不会VC++的),下面的资源为此软件的全套VC++源码及开发文档(共三份). 文本语音朗读组件包是一款TTS工具软件,它集成了对各种文本进行语音朗读的功能。但它不是一种传统意义上的TTS工具,它本身没有采用...

空空如也

空空如也

1 2 3 4 5 ... 9
收藏数 162
精华内容 64
关键字:

下面的函数会被多线程调用