精华内容
下载资源
问答
  • 什么线程同步

    千次阅读 2019-02-22 11:18:59
    线程有自己的私有数据,比如栈和寄存器,同时与其它线程共享相同的虚拟内存和全局变量等资源,当线程同时读写同一份共享资源的时候,会引起冲突,这时候就需要引入线程同步机制使各个线程排队一个一个的对共享...

    每一个服务进程的运行,都包含若干进程(Thread),线程是调度的基本单位,进程则是资源拥有的基本单位。

    线程有自己的私有数据,比如栈和寄存器,同时与其它线程共享相同的虚拟内存和全局变量等资源,当多个线程同时读写同一份共享资源的时候,会引起冲突,这时候就需要引入线程同步机制使各个线程排队一个一个的对共享资源进行操作,而不是同时进行。

    1.线程同步其实实现的是线程排队

    2.防止线程同步访问共享资源造成冲突。

    3.变量需要同步,常量不需要(常量存放于方法区)。

    4.多个线程访问共享资源的代码有可能是同一份代码,也有可能是不同的代码;无论是否执行同一份代码,只要这些线程的代码访问同一份可变的共享资源,这些线程之间就需要同步。

    展开全文
  • 多线程之间如何实现同步

    千次阅读 2019-07-01 11:56:53
    线程安全问题一般是发生再多线程环境,当多个线程同时共享一个全局变量或静态变量做写的操作时候,可能会发生数据冲突问题,也就是线程安全问题,在读的操作不会发生数据冲突问题 下面看个简单的买票例子 案例:需求...

    一、为什么会有线程安全问题?

    线程安全问题一般是发生再多线程环境,当多个线程同时共享一个全局变量或静态变量做写的操作时候,可能会发生数据冲突问题,也就是线程安全问题,在读的操作不会发生数据冲突问题 下面看个简单的买票例子 案例:需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。 代码:

    public class ThreadTrain1 implements Runnable { private int count = 100;
    
    @Override
    public void run() {
    	while (count > 0) {
    		try {
    			Thread.sleep(50);
    		} catch (Exception e) {
    			// TODO: handle exception
    		}
    		sale();
    	}
    }
    
    public void sale() {
    
    		System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
    		count--;
    	 }
    }
    
    
    }
    
    public class ThreadDemo { public static void main(String[] args) { ThreadTrain1 threadTrain1 = new ThreadTrain1(); Thread t1 = new Thread(threadTrain1, "①号窗口"); Thread t2 = new Thread(threadTrain1, "②号窗口"); t1.start(); t2.start(); } } 运行结果
    

    在这里插入图片描述

    我们可以发现一号窗口和二号窗口会卖出重复或者超卖现象,这就是在多线程环境下共享资源造成的线程安全问题

    (想自学习编程的小伙伴请搜索圈T社区,更多行业相关资讯更有行业相关免费视频教程。完全免费哦!)

    二、如何解决线程安全问题

    Synchronized(/'sɪŋkrənaɪzd/) ------相当于自动挡 Lock(/lɒk/ )—jdk1.5并发包才又 ------相当于手动挡

    1. 如何解决多线程之间线程安全问题? 答:使用多线程之间同步synchronized或使用锁(lock)。
    2. 为什么使用线程同步或使用锁能解决线程安全问题呢? 答:将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。代码执行完成后释放锁,让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。
    3. 什么是多线程之间同步? 答:当多个线程共享同一个资源,不会受到其他线程的干扰。
    4. 什么地方需要考虑枷锁? 答:真正产生共享同一个全局变量的时候。
    5. 锁是在什么时候释放的? 答:代码执行完毕或者是程序抛出异常,都会把锁释放掉

    三、同步

    3.1、什么是同步代码块? 答:就是将可能会发生线程安全问题的代码,给包括起来。 synchronized(同一个数据){ 可能会发生线程冲突问题 } 就是同步代码块 synchronized(对象) { //这个对象可以为任意对象 需要被同步的代码 } 对象如同锁,持有锁的线程可以在同步中执行 没持有锁的线程即使获取CPU的执行权,也进不去 同步的前提:

    1. 必须要有两个或者两个以上的线程
    2. 必须是多个线程使用同一个锁 必须保证同步中只能有一个线程在运行 好处:解决了多线程的安全问题 弊端:多个线程需要判断锁,较为消耗资源、抢锁的资源。 原理:有一个线程已经拿到了锁,其他线程已经有了cpu执行权,一直排队等待其他线程释放锁。 代码样例: private static Object oj = new Object(); public void sale() { // 前提 多线程进行使用、多个线程只能拿到一把锁。 // 保证只能让一个线程 在执行 缺点效率降低 synchronized (oj) { if (count > 0) { System.out.println(Thread.currentThread().getName() + “,出售第” + (100 - count + 1) + “票”); count–; } } } 4、同步函数 4.1、什么是同步函数? 答:在方法上修饰synchronized 称为同步函数 代码样例: public synchronized void sale() { if (trainCount > 0) { try { Thread.sleep(40); } catch (Exception e) { } System.out.println(Thread.currentThread().getName() + “,出售 第” + (100 - trainCount + 1) + “张票.”); trainCount–; } } 同学们思考问题?同步函数用的是什么锁? 答:同步函数使用this锁。 证明方式: 一个线程使用同步代码块(this明锁),另一个线程使用同步函数。如果两个线程抢票不能实现同步,那么会出现数据错误。 代码:
    package com.itmayiedu;
    class ThreadTrain2 implements Runnable { private int count = 100; public boolean flag = true; private static Object oj = new Object();
    
    @Override
    public void run() {
    	if (flag) {
    
    		while (count > 0) {
    
    			synchronized (this) {
    				if (count > 0) {
    					try {
    						Thread.sleep(50);
    					} catch (Exception e) {
    						// TODO: handle exception
    					}
    					System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
    					count--;
    				}
    			}
    
    		}
    
    	} else {
    		while (count > 0) {
    			sale();
    		}
    	}
    
    }
    
    public synchronized void sale() {
    	// 前提 多线程进行使用、多个线程只能拿到一把锁。
    	// 保证只能让一个线程 在执行 缺点效率降低
    	// synchronized (oj) {
    	if (count > 0) {
    		try {
    			Thread.sleep(50);
    		} catch (Exception e) {
    			// TODO: handle exception
    		}
    		System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
    		count--;
    	}
    	// }
    }
    
    }
    
    public class ThreadDemo2 { public static void main(String[] args) throws InterruptedException { ThreadTrain2 threadTrain1 = new ThreadTrain2(); Thread t1 = new Thread(threadTrain1, "①号窗口"); Thread t2 = new Thread(threadTrain1, "②号窗口"); t1.start(); Thread.sleep(40); threadTrain1.flag = false; t2.start(); } } 
    

    5、静态同步函数 5.1、什么是静态同步函数?

    1. 方法上加上static(/'stætɪk/)关键字,使用synchronized 关键字修饰 或者使用类.class文件。
    2. 静态的同步函数使用的锁是 该函数所属字节码文件对象
    3. 可以用 getClass方法获取,也可以用当前 类名.class 表示。 代码: synchronized (ThreadTrain.class) { System.out.println(Thread.currentThread().getName() + “,出售 第” + (100 - trainCount + 1) + “张票.”); trainCount–; try { Thread.sleep(100); } catch (Exception e) { } } 总结: synchronized 修饰方法使用锁是当前this锁。 synchronized 修饰静态方法使用锁是当前类的字节码文件 。 多线程死锁 3.1、什么是多线程死锁 答:同步中嵌套同步,导致锁无法释放 代码:
     package com.itmayiedu;
    class ThreadTrain6 implements Runnable { // 这是货票总票数,多个线程会同时共享资源 private int trainCount = 100; public boolean flag = true; private Object mutex = new Object();
    
    @Override
    public void run() {
    	if (flag) {
    		while (true) {
    			synchronized (mutex) {
    				// 锁(同步代码块)在什么时候释放? 代码执行完, 自动释放锁.
    				// 如果flag为true 先拿到 obj锁,在拿到this 锁、 才能执行。
    				// 如果flag为false先拿到this,在拿到obj锁,才能执行。
    				// 死锁解决办法:不要在同步中嵌套同步。
    				sale();
    			}
    		}
    	} else {
    		while (true) {
    			sale();
    		}
    	}
    }
    
    /**
     * 
     * @methodDesc: 功能描述:(出售火车票)
     */
    public synchronized void sale() {
    	synchronized (mutex) {
    		if (trainCount > 0) {
    			try {
    				Thread.sleep(40);
    			} catch (Exception e) {
    
    			}
    			System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
    			trainCount--;
    		}
    	}
    }
    
    }
    
    public class DeadlockThread {
    
    public static void main(String[] args) throws InterruptedException {
    
    	ThreadTrain6 threadTrain = new ThreadTrain6(); // 定义 一个实例
    	Thread thread1 = new Thread(threadTrain, "一号窗口");
    	Thread thread2 = new Thread(threadTrain, "二号窗口");
    	thread1.start();
    	Thread.sleep(40);
    	threadTrain.flag = false;
    	thread2.start();
    }
    
    }
    

    4、多线程有三大特性

    1. 原子性 一个操作或者多个操作要么全部执行要么就都不执行。
    2. 可见性 私有内存修改能及时同步到主内存,保证私有和主的可见一致性
    3. 有序性 程序执行的顺序按照代码的先后顺序执行。
      4.1、什么是原子性 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。 一个很经典的例子就是银行账户转账问题: 比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。这2个操作必须要具备原子性才能保证不出现一些意外的问题。 我们操作数据也是如此,比如i = i+1;其中就包括,读取i的值,计算i,写入i。这行代码在Java中是不具备原子性的,则多线程运行肯定会出问题,所以也需要我们使用同步和lock这些东西来确保这个特性了。 原子性其实就是保证数据一致、线程安全一部分,

    4.2、什么是可见性 当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。 若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。

    4.3、什么是有序性 程序执行的顺序按照代码的先后顺序执行。 一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。如下: int a = 10; //语句1 int r = 2; //语句2 a = a + 3; //语句3 r = a*a; //语句4 则因为重排序,他还可能执行顺序为 2-1-3-4,1-3-2-4 但绝不可能 2-1-4-3,因为这打破了依赖关系。 显然重排序对单线程运行是不会有任何问题,而多线程就不一定了,所以我们在多线程编程时就得考虑这个问题了。

    五、java内存模型

    共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
    在这里插入图片描述

    从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:

    1. 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
    2. 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。 下面通过示意图来说明这两个步骤:
      在这里插入图片描述
      如上图所示,本地内存A和B有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。线程A在执行时,把更新后的x值(假设值为1)临时存放在自己的本地内存A中。当线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变为了1。随后,线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变为了1。 从整体来看,这两个步骤实质上是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。 总结:什么是Java内存模型:java内存模型简称jmm,定义了一个线程对另一个线程可见。共享变量存放在主内存中,每个线程都有自己的本地内存,当多个线程同时访问一个数据的时候,可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题。

    六、volatile实现线程可见性

    Volatile 关键字的作用是变量在多个线程之间可见,但不保证原子性,下面的文章链接讲的非常好,这里不详细说了 juejin.im/post/5afd22…

    六、AtomicInteger原子类

    AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减。下面是个例子可以运行对比下count和atomicInteger的结果会发现无论运行多少次,atomicInteger的结果都是正确的 package com;

    import java.util.concurrent.atomic.AtomicInteger;
    
    public class VolatileNoAtomic extends Thread { static int count = 0; private static AtomicInteger atomicInteger = new AtomicInteger(0);
    
    @Override
    public  void  run() {
    	for (int i = 0; i < 1000; i++) {
    		//等同于i++
    		atomicInteger.incrementAndGet();
    		count++;
    	}
    	System.out.println(atomicInteger+","+count);
    }
    
    public static void main(String[] args) {
    	// 初始化10个线程
    	VolatileNoAtomic[] volatileNoAtomic = new VolatileNoAtomic[10];
    	for (int i = 0; i < 10; i++) {
    		// 创建
    		volatileNoAtomic[i] = new VolatileNoAtomic();
    	}
    	for (int i = 0; i < volatileNoAtomic.length; i++) {
    		volatileNoAtomic[i].start();
    	}
    	} 
    }
    

    上面只是简单的一个原子类的介绍

    七、volatile(/'vɒlətaɪl/)与synchronized(/'sɪŋkrənaɪzd/)区别

    仅靠volatile不能保证线程的安全性。(原子性) ①volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法 ②volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞。 synchronized不仅保证可见性,而且还保证原子性,因为,只有获得了锁的线程才能进入临界区,从而保证临界区中的所有语句都全部执行。多个线程争抢synchronized锁对象时,会出现阻塞。 线程安全性 线程安全性包括两个方面,①可见性。②原子性。 从上面自增的例子中可以看出:仅仅使用volatile并不能保证线程安全性。而synchronized则可实现线程的安全性。 八、ThreadLocal深度解析

    展开全文
  • python的多线程及线程同步方式

    千次阅读 2019-07-17 09:37:23
    1.线程执行 join与setDaemon 1.子线程在主线程运行结束后,会继续执行完,如果给子线程设置为守护线程(setDaemon=True),主线程运行结束子线程即结束; 2 .如果join()线程,那么主线程会等待子线程执行完再执行...

    1.线程执行

    join与setDaemon

    • 1.子线程在主线程运行结束后,会继续执行完,如果给子线程设置为守护线程(setDaemon=True),主线程运行结束子线程即结束;

    • 2 .如果join()线程,那么主线程会等待子线程执行完再执行。

    import threading
    import time
    
    
    def get_thread_a():
        print("get thread A started")
        time.sleep(3)
        print("get thread A end")
    
    
    def get_thread_b():
        print("get thread B started")
        time.sleep(5)
        print("get thread B end")
    
    
    if  __name__ == "__main__":
        thread_a = threading.Thread(target=get_thread_a)
        thread_b = threading.Thread(target=get_thread_b)
        start_time = time.time()
        thread_b.setDaemon(True)
        thread_a.start()
        thread_b.start()
        thread_a.join()   
    
        end_time = time.time()
        print("execution time: {}".format(end_time - start_time))
    

    thread_a是join,首先子线程thread_a执行,thread_b是守护线程,当主线程执行完后,thread_b不会再执行

    执行结果如下:

    get thread A started
    get thread B started
    get thread A end
    execution time: 3.003199815750122
    

    2.线程同步

    多线程间共享全局变量,多个线程对该变量执行不同的操作时,该变量最终的结果可能是不确定的(每次线程执行后的结果不同),如:对count变量执行加减操作 ,count的值是不确定的,要想count的值是一个确定的需对线程执行的代码段加锁。

    3.线程同步的方式

    3.1 锁机制

    在这里插入图片描述
    python对线程加锁主要有Lock和Rlock模块

    Lock:

    from threading import Lock
     
    lock = Lock()
    lock.acquire()
    lock.release()
    

    Lock有acquire()和release()方法,这两个方法必须是成对出现的,acquire()后面必须release()后才能再acquire(),否则会造成死锁

    Rlock:

    鉴于Lock可能会造成死锁的情况,RLock(可重入锁)对Lock进行了改进,RLock可以在同一个线程里面连续调用多次acquire(),但必须再执行相同次数的release()

    from threading import RLock
    
    lock = RLock()
    lock.acquire()
    lock.acquire()
    lock.release()
    lock.release()
    

    当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“同步阻塞”(参见多线程的基本概念)。

    直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。

    3.2 Semaphore(信号量)

    信号量也提供acquire方法和release方法,每当调用acquire方法的时候,如果内部计数器大于0,则将其减1,如果内部计数器等于0,则会阻塞该线程,直到有线程调用了release方法将内部计数器更新到大于1位置。

    Semaphore(信号量)是计算机科学史上最古老的同步指令之一。Semaphore管理一个内置的计数器,每当调用acquire()时-1,调用release() 时+1。计数器不能小于0;当计数器为0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。

    基于这个特点,Semaphore经常用来同步一些有“访客上限”的对象,比如连接池。

    BoundedSemaphore 与Semaphore的唯一区别在于前者将在调用release()时检查计数器的值是否超过了计数器的初始值,如果超过了将抛出一个异常。

    构造方法:
    Semaphore(value=1): value是计数器的初始值。

    import time
    import threading
    
    
    def get_thread_a(semaphore,i):
        time.sleep(1)
        print("get thread : {}".format(i))
        semaphore.release()
    
    
    def get_thread_b(semaphore):
        for i in range(10):
            semaphore.acquire()
            thread_a = threading.Thread(target=get_thread_a, args=(semaphore,i))
            thread_a.start()
    
    
    if __name__ == "__main__":
        semaphore = threading.Semaphore(2)
        thread_b = threading.Thread(target=get_thread_b, args=(semaphore,))
        thread_b.start()
    

    3.3 条件判断

    所谓条件变量,即这种机制是在满足了特定的条件后,线程才可以访问相关的数据。
      
    它使用Condition类来完成,由于它也可以像锁机制那样用,所以它也有acquire方法和release方法,而且它还有wait,notify,notifyAll方法。
    在这里插入图片描述

    """
    一个简单的生产消费者模型,通过条件变量的控制产品数量的增减,调用一次生产者产品就是+1,调用一次消费者产品就会-1.
    """
    
    """
    使用 Condition 类来完成,由于它也可以像锁机制那样用,所以它也有 acquire 方法和 release 方法,而且它还有
    wait, notify, notifyAll 方法。
    """
    
    import threading
    import queue,time,random
    
    class Goods:#产品类
        def __init__(self):
            self.count = 0
        def add(self,num = 1):
            self.count += num
        def sub(self):
            if self.count>=0:
                self.count -= 1
        def empty(self):
            return self.count <= 0
    
    class Producer(threading.Thread):#生产者类
        def __init__(self,condition,goods,sleeptime = 1):#sleeptime=1
            threading.Thread.__init__(self)
            self.cond = condition
            self.goods = goods
            self.sleeptime = sleeptime
        def run(self):
            cond = self.cond
            goods = self.goods
            while True:
                cond.acquire()#锁住资源
                goods.add()
                print("产品数量:",goods.count,"生产者线程")
                cond.notifyAll()#唤醒所有等待的线程--》其实就是唤醒消费者进程
                cond.release()#解锁资源
                time.sleep(self.sleeptime)
    
    class Consumer(threading.Thread):#消费者类
        def __init__(self,condition,goods,sleeptime = 2):#sleeptime=2
            threading.Thread.__init__(self)
            self.cond = condition
            self.goods = goods
            self.sleeptime = sleeptime
        def run(self):
            cond = self.cond
            goods = self.goods
            while True:
                time.sleep(self.sleeptime)
                cond.acquire()#锁住资源
                while goods.empty():#如无产品则让线程等待
                    cond.wait()
                goods.sub()
                print("产品数量:",goods.count,"消费者线程")
                cond.release()#解锁资源
    
    g = Goods()
    c = threading.Condition()
    
    pro = Producer(c,g)
    pro.start()
    
    con = Consumer(c,g)
    con.start()
    

    Condition内部有一把锁,默认是RLock,在调用wait()和notify()之前必须先调用acquire()获取这个锁,才能继续执行;当wait()和notify()执行完后,需调用release()释放这个锁,在执行with condition时,会先执行acquire(),with结束时,执行了release();所以condition有两层锁,最底层锁在调用wait()时会释放,同时会加一把锁到等待队列,等待notify()唤醒释放锁

    wait() :允许等待某个条件变量的通知,notify()可唤醒

    notify(): 唤醒等待队列wait()

    # encoding: UTF-8
    import threading
    import time
     
    # 商品
    product = None
    # 条件变量
    con = threading.Condition()
     
    # 生产者方法
    def produce():
        global product
        
        if con.acquire():
            while True:
                if product is None:
                    print 'produce...'
                    product = 'anything'
                    
                    # 通知消费者,商品已经生产
                    con.notify()
                
                # 等待通知
                con.wait()
                time.sleep(2)
     
    # 消费者方法
    def consume():
        global product
        
        if con.acquire():
            while True:
                if product is not None:
                    print 'consume...'
                    product = None
                    
                    # 通知生产者,商品已经没了
                    con.notify()
                
                # 等待通知
                con.wait()
                time.sleep(2)
     
    t1 = threading.Thread(target=produce)
    t2 = threading.Thread(target=consume)
    t2.start()
    t1.start()
    

    3.4 同步队列

    put方法和task_done方法,queue有一个未完成任务数量num,put依次num+1,task依次num-1.任务都完成时任务结束。
      在这里插入图片描述

    import threading
    import queue
    import time
    import random
    
    '''
    1.创建一个 Queue.Queue() 的实例,然后使用数据对它进行填充。
    2.将经过填充数据的实例传递给线程类,后者是通过继承 threading.Thread 的方式创建的。
    3.每次从队列中取出一个项目,并使用该线程中的数据和 run 方法以执行相应的工作。
    4.在完成这项工作之后,使用 queue.task_done() 函数向任务已经完成的队列发送一个信号。
    5.对队列执行 join 操作,实际上意味着等到队列为空,再退出主程序。
    '''
    
    class jdThread(threading.Thread):
        def __init__(self,index,queue):
            threading.Thread.__init__(self)
            self.index = index
            self.queue = queue
    
        def run(self):
            while True:
                time.sleep(1)
                item = self.queue.get()
                if item is None:
                    break
                print("序号:",self.index,"任务",item,"完成")
                self.queue.task_done()#task_done方法使得未完成的任务数量-1
    
    q = queue.Queue(0)
    '''
    初始化函数接受一个数字来作为该队列的容量,如果传递的是
    一个小于等于0的数,那么默认会认为该队列的容量是无限的.
    '''
    for i in range(2):
        jdThread(i,q).start()#两个线程同时完成任务
    
    for i in range(10):
        q.put(i)#put方法使得未完成的任务数量+1
    

    3.5 Event对象

    Event对象是一种简单的线程同步通信技术,一个线程设置Event对象,另一个线程等待Event对象。
    在这里插入图片描述

    import threading
    
    
    # 自定义线程类
    class MyThread(threading.Thread):
        def __init__(self, thread_name):
            threading.Thread.__init__(self, name=thread_name)
    
        # 重写线程代码
        def run(self):
            global my_event
            if my_event.isSet():
                my_event.clear()
                # 等待通知
                my_event.wait()
                print(self.getName())
            else:
                print(self.getName())
                my_event.set()
    
    
    # 创建锁
    my_event = threading.Event()
    my_event.set()
    t1 = []
    
    for i in range(10):
        t = MyThread(str(i))
        t1.append(t)
    
    for t in t1:
        t.start()
    
    
    展开全文
  • 多线程间的通信和同步

    千次阅读 多人点赞 2019-06-15 11:21:07
    一、什么多线程? 二、为什么要创建线程 三、线程之间如何通信 四、线程安全 五、线程的同步 (一)互斥量mutex (二)临界区 critical section (三)信号量 semaphore (四)事件 event 一、什么...

    最近看了很多关于网络编程和多线程的书,为了以后查看相关内容方便,整理了几本书的精华形成这篇博文,希望能帮助观看这篇博文的读者。

    目录

    一、什么是多线程?

    二、为什么要创建线程

    三、线程之间如何通信

    四、线程安全

    五、线程的同步

    (一)互斥量mutex

    (二)临界区 critical section

    (三)信号量 semaphore

    (四)事件 event


    一、什么是多线程?

    再说多线程之前引入多进程的概念。那什么是进程?在 Windows 系统中,我们每开一个应用程序系统就为其开辟一个进程,就比如我们打开一个 Word 文档就是一个进程,如果再此基础上按 control + N 在新建个 Word 文档这就开了两个进程。

    其中每个进程的内存空间都有保存全局变量的“数据区”、像 malloc / new 等函数的动态分配提供空间的堆(Heap)、函数运行时使用的栈(Stack)构成。每个进程都拥有这样的独立空间,多个进程的内存结构可以参考下图。

    但如果以获得多个代码执行流为主要目的,就不行该这样分离内存结构,而只需要分离栈区域。这样可以有如下优点:

    • 上下文切换时(这里指进程间的切换)不需要切换数据区和堆
    • 可以利用数据区和堆交换数据

    实现以上目地的方法就是多线程,就像我们打开一个 Word 文档,在里面同时编辑 sheet1,sheet2 一样,每一个 sheet 就是一个线程。线程为了保持多条代码执行流而隔离开了栈区域,因此具有如下图的结构:

    二、为什么要创建线程

    通过上面的讲解我们知道了,多线程是能够在一个应用程序中进行多个任务。比如我们要打印 1000 页的Word,如果只有一个线程,那么我们在打印结束前是不可以对 Word 进行操作的,而且打印1000 页要耗费很多时间。但是,实际并不是如此,我们在打印的时候依然可以对 Word 进行编辑操作,这就是多线程的一种应用,处理耗时程序。同样的应用还用在数据采集当中。等

    三、线程之间如何通信

    在 Windows 系统中线程之间的通信有两种方式

    • 使用全局变量进行通信
    • 使用自定义消息进行通信

    在第一部分中我们介绍了,线程的数据区和堆区域是共享的,所以我们可以声明全局变量来进行线程之间的通信和数据交换。如果线程之间传递的数据比较复杂,我们可以定义一个结构,通过传递指向该结构的指针进行消息传递。接着让线程监视这个变量,当这个变量符合一定的条件时,表示该线程终止。

    使用自定义消息暂时不做解释,是 Windows 编程中MFC 的内容,如果有读者和我同样学习 MFC 请在文章下面留言,我在补充相关内容。下面给出基于 Linux 系统的代码。

    //
    //  main.cpp
    //  thread
    //
    //  Created by 刘一丁 on 2019/6/15.
    //  Copyright © 2019年 LYD. All rights reserved.
    //  本示例用的是 Linus 系统的编程语言,如有需要 Windows 编程语言的请在博文中留言,博主在补齐。
    //  本示例存在线程间通信的安全问题,即同时访问同一存储区变量(临界区),解决这个问题可以用线程间的同步
    //  函数功能:通过两个线程分别计算 1-5、6-10 的和,并返回其值
    
    #include <stdio.h>
    #include <pthread.h>
    
    void *thread_summation(void *arg);         //声明线程
    int sum = 0;                               //线程间通信用的全局变量
    
    int main(int argc, const char * argv[])
    {
        pthread_t id_t1, id_t2;
        int range1[] = {1, 5};
        int range2[] = {6, 10};
        
        pthread_create(&id_t1, nullptr, thread_summation, (void*)range1);    //创建线程
        pthread_create(&id_t2, nullptr, thread_summation, (void*)range2);
        
        pthread_join(id_t1, NULL);                                           //控制线程的执行流
        //调用该函数的线程将进入等待状态,直到第一个参数 ID 的线程终止为止。
        pthread_join(id_t2, NULL);
        
        return 0;
    }
    
    void *thread_summation(void *arg)
    {
        int start = ((int*)arg)[0];
        int end = ((int*)arg)[1];
        
        while(start <= end)
        {
            sum += start;             //这里设计到对 sum 值的访问
            start++;
        }
        return NULL;
    }
    
    

    流程图如下所示:

    四、线程安全

    在第三部分我已经提出了示例中存在的临界区问题,该问题的发生是有概率的,和电脑系统配置有关,运行结果可能因机器而异。那么怎么产生的这个问题呢?

    上述示例中两个线程同时访问变量 sum,这里的访问指的是对 sum 的值的更改。除此之外例如,对于像磁盘驱动器这样独占性系统资源,由于线程可以执行进程的任何代码段,且线程的运行是由系统调度自动完成的,具有一定的不确定性,因此就有可能出现两个线程同时对磁盘驱动器进行操作,从而出现上面的错误。再比如,对于银行系统的计算机来说,可能使用一个线程来更新其用户数据库,而用另外一个线程来读取数据库以响应储户需求,极有可能读数据库的线程读取的是未完全更新的数据库,因为可能在读的时候只有一部分数据被更新过。具体解释涉及到 CPU 和内存管理,再此略,如果感兴趣的读者,可以自行查找相关文献书籍。

    那么怎么解决这个问题呢?

    线程间的同步就可以解决这个问题。

    五、线程的同步

    使隶属于同一进程的线程协调一致的工作就是线程间的同步。在多线程环境里,需要对线程进行同步。常用的同步对象有临界区(Critical Section)、互斥(Mutex)、信号量(Semaphore)和事件(event)等。用于解决线程访问顺序引发的问题。需要同步的情况可以从以下两方面考虑:

    • 同时访问同一内存空间时发生的情况。
    • 需要指定访问同一内存空间的线程执行顺序的情况。
    支持多线程同步的同步类
    类型说明
    Critical Section当在一个时间内仅有一个线程被允许修改数据或其某些其他控制资源时使用,用于保护共享资源(比如写数据)
    Mutex当多个应用(多个进程)同时存取相应资源时使用,用于保护共享资源
    Semaphore一个应用允许同时有多个线程访问相应资源时使用(比如读数据),主要功能用于资源计数
    event某个线程必须等待某些事件发生后才能存取相应资源时使用,以协调线程之间的动作。

    (一)互斥量mutex

    下面介绍互斥量的使用方法(基于 Windows,Linux 系统下步骤也是一样的)

    • 定义  CMutex 类的一个全局对象(以使各个线程均能访问),如

    CMutex mutex;

    • 在访问临界区之前,调用 mutex 类的成员 Lock()获得临界区

    mutex.Lock();

    在线程中调用该函数来使线程获得它所请求的临界区。如果此时没有其他线程占有临界区,则调用 Lock()的线程获得临界区;否则,线程即将挂起,并放入到一个系统队列中等待,直到当前拥有临界区的线程释放了临界区时为止。

    • 在本线程中访问临界区中的共享资源
    • 访问临界区完毕后,使用CMutex 的成员函数 UnLock()来释放临界区。

    mutex.UnLock();

    对于 Linux 系统来说直接给出函数使用过程

    • 声明 mutex 全局变量
    • 创建互斥量
    • 获得临界区
    • 访问共享资源
    • 释放互斥量

    下面给出 Linux 系统下上面示例的改进版,请读者自行分析,pthread_mutex_lock();放在 while 循环里面和 while 循环外面的区别,如果有兴趣可以在博文下留言讨论。

    //
    //  main.cpp
    //  thread
    //
    //  Created by 刘一丁 on 2019/6/15.
    //  Copyright © 2019年 LYD. All rights reserved.
    //  本示例用的是 Linus 系统的编程语言,如有需要 Windows 编程语言的请在博文中留言,博主在补齐。
    
    #include <stdio.h>
    #include <pthread.h>
    
    void *thread_summation(void *arg);         //声明线程
    int sum = 0;                               //线程间通信用的全局变量
    pthread_mutex_t mutex;                     //声明 mutex 变量
    
    int main(int argc, const char * argv[])
    {
        pthread_t id_t1, id_t2;
        int range1[] = {1, 5};
        int range2[] = {6, 10};
        
        pthread_mutex_init(&mutex, NULL);       //创建互斥量
        
        pthread_create(&id_t1, nullptr, thread_summation, (void*)range1);    //创建线程
        pthread_create(&id_t2, nullptr, thread_summation, (void*)range2);
        
        pthread_join(id_t1, NULL);                                           //控制线程的执行流
        //调用该函数的线程将进入等待状态,直到第一个参数 ID 的线程终止为止。
        pthread_join(id_t2, NULL);
        
        printf("result: %d\n", sum);
        pthread_mutex_destroy(&mutex);           //释放互斥量
        return 0;
    }
    
    void *thread_summation(void *arg)
    {
        int start = ((int*)arg)[0];
        int end = ((int*)arg)[1];
        
        while(start <= end)
        {
            pthread_mutex_lock(&mutex);          //获得临界区
            sum += start;                        //访问共享资源
            start++;
            pthread_mutex_unlock(&mutex);
        }
        return NULL;
    }
    
    //output:
    //result: 55
    
    

    讨论:互斥量中的 Lock() 是怎么工作的?从以下 3个方面来解释。

    1.内核对象

    操作系统创建的资源(Resource)有很多种,如进程、线程、文件及刚刚介绍的互斥量和即将介绍的临界区、信号量等。不同资源类型在“管理”方式上也有差异。例如,文件管理中应注册并更新文件相关的数据 I/O 位置、文件的打开方式(read or write)等。如果是线程,则应注册并维护线程 ID、线程所属进程等信息。操作系统为了以记录相关信息的方式管理各种资源,在其内部生成数据块(相当于结构体)。当然,每种资源需要维护的信息不同,所以每种资源拥有的数据块格式也不相同。这类数据块称为“内核对象”。

    2.内核对象的两种状态

    资源类型不同,内核对象也含有不同的信息。其中,应用程序实现过程中需要特别关注的信息被赋予某种“状态”(state)。例如,线程内核对象中需要重点关注线程是否已经终止,所以终止状态又称“signaled 状态”(其他线程可访问),未终止状态成为“non-signaled 状态”(其他线程不可访问)。同时,操作系统会在进程或线程终止时,把相应的内核对象改为 signaled 状态。

    3.互斥量内核的状态

    在基于互斥量的同步中将创建互斥量 mutex对象。与其他同步对象相同,它是进入临界区的一把“钥匙”。因此,为了进入临界区,需要得到mutex 对象这把钥匙(Lock)。相反离开时需要上交 mutex 对象(unlock)。

    互斥量被某一线程获取时(Lock)为 non-signaled 状态,释放时(unlock)进入 signaled 状态。因此,可以使用 Lock 和 unlock 来验证互斥量是否已经被分配。Lock函数的调用结果有如下2种。

    • 调用后进入阻塞状态:互斥量对象已被其他线程获取,现处于 non-signaled 状态。
    • 调用后直接返回:其他线程未占用互斥量对象,现处于signaled 状态。

    (二)临界区 critical section

    临界区的使用规则和互斥量完全相同,在这里不在讨论,二者的区别也在上文表格中体现,一个用在线程之间,一个用除了线程还可以用在进程之间。

    (三)信号量 semaphore

    信号量的使用方法也和互斥量相同,步骤是一样的。所以博主在这里介绍信号量的另一种使用方法“二进制信号量”,又称为“控制线程顺序”的同步方法。

    在 Windows 系统下信号量和互斥量唯一区别的就是构造函数,其他用法一样,下面给出 Windows 系统下 semaphore 的构造函数

    1.CSemaphore(LONG lInitialCount = 1,
               LONG lMaxCount = 1,
               LPCTSTR pstrName = NULL,
               LPSECURITY_ATTRIBUTES lpsaAttributes = NULL);
    
    →lInitialCount - 信号量对象的初始计数值,即可访问线程数目的初始值
    →lMaxCount - 信号量对象计数值的最大值,该参数决定了同一时刻可访问由信号量保护的资源的线程最大数目
    
    2.BOOL ReleaseSemaphone(HANDLE hSemaphone, LONG lReleaseCount, LPLONG lpPreviousCount);
    
    →成功时返回 TRUE,失败时返回 FALSE
    →hSemaphone - 传递需要释放的信号量对象
    →lReleaseCount -  释放意味着信号量值的增加,通过该参数可以指定增加的值。
                     超过最大值则不增加,返回 FALSE
    →lpPreviousCount - 用于保存修改之前值的变量地址,不需要时可传递 NULL。
    
    注:信号量对象的值大于 0 时成为 signaled 状态,为 0 时成为 non-signaled 状态。
    因此,调用 WaitForSingleObject 函数时,信号量大于 0 的情况才会返回。
    返回的同时将信号量值减 1,同时进入 non-signaled 状态(当然,仅限于信号量减 1 后等于 0 的情况)。
    
    
    执行 WaitForSingleObject(hSemaphone, INFINITE);时-1
    执行 ReleaseSemaphone()时+1,为 0 时阻塞
    
    WaitForSingleSemaphone(hSemaphone, INFINITE)
    //临界区的开始
    //..........
    //临界区的结束
    ReleaseSemaphone(hSemaphone, 1, NULL);

    在 Linux 系统下,首先给出相当于互斥量 Lock、UnLock 的函数。

    #include <pthread.h>
    
    int sem_post(sem_t * sem);   //+1
    int sem_wait(sem_t * sem);   //-1,为0 时阻塞
    
    成功时返回 0,失败时返回其他值。
    sem - 传递保存信号量读取值的变量地址,传递给 sem_post 时信号量增 1,传递给 sem_wait 时信号量减 1.

    调用sem_init 函数(略)时,操作系统将创建信号量对象,此对象中记录着“信号量值”整数。该值在调用 sem_post 函数时增 1,在调用 sem_wait函数时减 1。但信号量的值不能小于 0,因此,在信号量为 0 的情况下调用 sem_wait 函数时,调用函数的线程将进入阻塞状态(因为函数未返回)。当然,此时如果有其他函数线程调用 sem_post 函数,信号量的值将变为 1,而原本阻塞的线程可以将该信号量重新减为 0 并跳出阻塞状态。实际上就是通过这种特性完成临界区的同步操作,可以通过如下形式同步临界区(假设信号量的初始值为 1)。

    sem_wait(&sem);    //信号量变为 0.
    //临界区的开始
    //.......
    //临界区结束
    sem_post(&sem);    //信号量变为 1.

    上述代码结构中,调用 sem_wait 函数进入临界区的线程在调用 sem_post 函数前不允许其他线程进入临界区。信号量的值在 0 和 1 之间跳转,这种特性就是“二进制信号量”。下面给出基于 Linux 系统的代码示例。

    //
    //  main.cpp
    //  thread
    //
    //  Created by 刘国栋 on 2019/6/15.
    //  Copyright © 2019年 LGD. All rights reserved.
    //  本示例用的是 Linus 系统的编程语言,如有需要 Windows 编程语言的请在博文中留言,博主在补齐。
    //  本示例实现“线程 A从用户输入得到值后存入全局变量 num,此时线程 B 取走该值并累加。该过程共进行
    //  5 次,完成后输出总和并推出程序”
    
    #include <stdio.h>
    #include <pthread.h>
    #include <semaphore.h>
    
    void * read(void * arg);
    void * accu(void * arg);
    static sem_t sem_one;
    static sem_t sem_two;
    static int num;
    
     int main(int argc, const char * argv[])
    {
        pthread_t id_t1, id_t2;
        sem_init(&sem_one, 0, 0);
        sem_init(&sem_two, 0, 1);
        
        pthread_create(&id_t1, NULL, read, NULL);
        pthread_create(&id_t2, NULL, accu, NULL);
        
        pthread_join(id_t1, NULL);
        pthread_join(id_t2, NULL);
        
        sem_destroy(&sem_one);
        sem_destroy(&sem_two);
        return 0;
    }
    
    void * read(void * arg)
    {
        for(int i = 0; i < 5; i++)
        {
            fputs("Input num: ", stdout);
            
            sem_wait(&sem_two);
            scanf("%d", &num);
            sem_post(&sem_one);
        }
        return NULL;
    }
    
    void * accu(void * arg)
    {
        int sum = 0;
        for(int i = 0; i < 5; i++)
        {
            sem_wait(&sem_one);
            sum += sum;
            sem_post(&sem_two);
        }
        printf("Result: %d\n", sum);
        return NULL;
    }
    
    /*
    运行结果:semaphore.c
    root@my_linux:/tcpip# gcc semaphore.c -D_REENTRANT -o sema -lpthread
    root@my_linux:/tcpip# ./sema
    Input num:1
    Input num:2
    Input num:3
    Input num:4
    Input num:5
    Result: 15
    */

    上述代码请读者自行分析,有疑问可以在博文下方留言,博主会为其解答。还请读者特别注意分析 24-25 行 44-46、56-58 行代码的使用方式和所达到“二进制信号量”功能的实现。

    (四)事件 event

    事件同步对象与前2 种同步方法相比有很大不同,区别就在于,该方式下创建对象时,可以在自动以 non-signaled 状态运行的 auto-reset 模式和与之相反的 manual-reset 模式汇总任选其一。而事件对象的主要特点是可以创建 manual-reset 模式的对象。

    在 Windows 环境下,介绍创建事件对象的函数。

    #include <windows.h>
    
    HANDLE CreateEvent(
        LPSECURITY_ATTRIBUTS lpEventAttributes, BOOL bManualReset,
        BOOL bIntialState, LPCTSTR lpName);
    
    →成功时返回创建的事件对象句柄,失败时返回 NULL
    →lpEventAttributes  安全配置相关参数,采用默认安全时传入 NULL
    →bManualReset  传入 TRUE 时创建 manual-reset 模式的事件对象,传入 FALSE 时创建auto-reset 模式的事件对象。
    →bIntialState  传入 TURE 时创建 signaled 状态的事件对象,传入 FALSE 时创建 non-signaled 状态的事件对象。
    →lpName  用于命名事件对象。传递 NULL 时创建无名的事件对象

    在介绍一个函数 WaitForSingleObject 函数,该函数针对单个内核对象验证 signaled状态。

    #include <windows.h>
    
    DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
    
    →成功时返回事件信息,失败时返回 WAIT——FAILED
    →hHandle  查看状态的内核对象句柄。
    →dwMilliseconds  以 1/1000 秒为单位指定超时,传递 INFINTE 时函数不会返回,直到内核对象变成 signaled 状态。
    →返回值  进入 signaled 状态返回 WAIT_OBJECT_0,超时返回 WAIT_TIMEOUT。

    上面这个函数其实就相当于 Lock 的功能,只有当要查看对象的状态为 signaled 时才会有返回值(超时也会返回),否则一直等待(阻塞)。同时该函数由于发生时间(变为signaled状态)返回时,有时会把相应内核对象再次改为 non-signaled 状态。这种可以再次进入 non-signaled 状态的内核对象称为“auto-reset 模式”的内核对象,而不会自动跳转到 non-signaled 状态的内核对象称为“ manual-reset模式”的内核对象。

    就如创建实现对象的初始化函数 CreateEvent 的第二个参数。传入 TURE 时创建 manual-reset 模式的事件对象,此时即使 WaitForSingleObject 函数返回也不会回到 non-signaled 状态。因此,需要通过下面两个函数明确更改对象状态。

    #include <windows.h>
    
    BOOL ResetEvent(HANDLE hEvent);  //to the non-signaled
    BOOL SetEvent(HANDLE hEvent);    //to the signaled
    
    →成功时返回 TURE,失败时返回 FALSE

    所以,传递事件对象句柄并希望改为 non-signaled状态时,应调用 ResetEvent 函数。如果希望改为signaled 状态,则可以调用 SetEvent 函数。下面给出基于 Windows 的示例

    //
    //  main.cpp
    //  thread
    //
    //  Created by 刘国栋 on 2019/6/19.
    //  Copyright © 2019年 LGD. All rights reserved.
    //
    //  示例中的两个线程同时等待输入字符串
    
    #include <stdio.h>
    #include <windows.h>
    #include <process.h>
    #define STR_LEN 100
    
    unsigned WINAPI NumberOfA(void *arg);
    unsigned WINAPI NumberOfOthers(void *arg);
    
    static char str[STR_LRN];
    static HANDLE hEvent;
    
    int main(int argc, const char *srgv[])
    {
        HANDLE hThread1, hThread2;
        hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
        hThread1 = (HANDLE)_beginthreadex(NULL, 0, NumberOfA, NULL, 0, NULL);
        hThread2 = (HANDLE)_beginthreadex(NULL, 0, NumberOfOthers, NULL, 0, NULL);
        
        fputs("input string: ", stdout);
        fgets(str, STR_LEN, stdin);
        SetEvent(hEvent);
        
        WaitForSingleObjevt(hThread1, INFINITE);
        WaitForSingleObjevt(hThread2, INFINITE);
        ResetEvent(hEvent);
        CloseHandle(hEvent);
        return 0;
    }
    
    unsigned WINAPI NumberOfA(void *arg)
    {
        int i, cnt = 0;
        WaitForSingleObjevt(hEvent, INFINITE);
        for(i = 0; str[i] != 0; i++)
            if(str[i] == 'A')
                cnt++;
        printf("Num of A: %d \n", cnt);
        return 0;
    }
    
    unsigned WINAPI NumberOfOthers(void *arg)
    {
        int i, cnt = 0;
        WaitForSingleObjevt(hEvent, INFINITE);
        for(i = 0; str[i] != 0; i++)
            if(str[i] != 'A')
                cnt++;
        printf("Num of Others: %d \n", cnt);
        return 0;
    }
    
    //output
    //Input string: ABCDABC
    //Num of A: 2
    //Num of others: 5
    

    →第 24 行:以 non-signaled 状态创建manual-reset 模式的事件对象。

    →第 25、26 行:创建两个线程,NumOfA  and  NumOfOthers 同时开始执行,分别执行到 42 行 53 行进入阻塞状态。

    →第 30 行:读入字符串后将事件对象改为signaled 状态。第 42、53 行中正在等待的2个线程将摆脱等待状态,开始执行。2 个线程之所以可以同时摆脱等待状态是因为事件对象仍处于signaled 状态。

    →第 32、33 行:注意此处的 WaitForSingleObject 传递的句柄是线程的句柄,只有等到线程返回时,该线程的句柄才会由 non-signaled 状态编程 signaled 状态,WaitForSingleObject 才会返回,否则处于阻塞状态。

    →第 34 行:虽然在本例子中该语句没太大必要,但还是把事件对象的状态改为 non-signaled。如果不进行明确更改,对象将继续停留在 signaled 状态。

    展开全文
  • 什么线程同步线程异步?

    千次阅读 2019-05-31 13:41:39
    线程异步:访问资源时,如果有空闲时间,则可在空闲等待同时访问其他资源,实现多线程机制 异步处理就是,你现在问我问题,我可以不回答你,等到我有时间了再处理你这个问题,同步就是要立即处理这个问题,直到信息...
  • JAVA多线程——实现同步

    千次阅读 多人点赞 2018-07-26 17:20:33
    java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用, 从而保证了...
  • 同步多线程

    千次阅读 2013-11-02 08:50:57
    同步多线程(SMT)是一种在一个CPU 的时钟周期内能够执行来自多个线程的指令的硬件多线程技术。本质上,同步多线程是一种将线程级并行处理(多CPU)转化为指令级并行处理(同一CPU)的方法。 同步多线程是单个物理...
  • 当使用多线程访问同一个资源的时候,非常容易出现线程安全的问题(例如,当多个线程同时对一个数据进行修改的时候,会导致某些线程对数据的修改丢失)。 因此,需要采用同步机制来解决这种问题。而Java主要提供了三...
  • 【Java多线程-6】synchronized同步

    千次阅读 2020-03-31 15:35:49
    前文描述了Java多线程编程,多线程的方式提高了系统资源利用和程序效率,但多个线程同时处理共享的数据时,就将面临线程安全的问题。 例如,下面模拟这样一个场景:一个售票处有3个售票员,出售20张票。 public ...
  • 什么多线程?如何实现多线程

    万次阅读 多人点赞 2019-04-09 09:53:36
    【转】什么线程安全?怎么实现线程安全?什么是进程?什么线程什么线程安全?添加一个状态呢?如何确保线程安全?synchronizedlock 转自:https://blog.csdn.net/csdnnews/article/details/82321777 什么是...
  • JAVA多线程之间实现同步+多线程并发同步解决方案

    万次阅读 多人点赞 2018-03-04 14:09:15
    一、什么是线程安全问题 为什么...案例:需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。/** * 需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。 * Crea...
  • Java 多线程 —— 同步代码块(解决线程安全问题)

    千次阅读 多人点赞 2021-10-23 19:59:26
    目录火车站抢票问题同步代码块同步方法(this锁)同步方法,在public的后面加上synchronized关键字this锁静态同步方法 火车站抢票问题 由于现实中买票也不会是零延迟的,为了真实性加入了延迟机制,也就是线程休眠...
  • C#多线程——线程同步

    万次阅读 2018-08-25 13:11:53
    一、为什么线程同步线程同时使用共享对象会造成很问题,同步这些线程使得对共享对象的操作能够以正确的顺序执行是非常重要的。 二、实现线程同步的方法: • 使用Mutex类 • 使用SemaphoreSlim类 • ...
  • Java多线程同步优化的6种方案

    万次阅读 2020-06-24 17:44:34
    Java中可以使用锁来解决多线程同步问题,保障了数据的一致性,但也会代理很多问题,本章总结了多线程同步的几种优化方案:包括读写锁、写时复制机制、锁细化等方案。
  • 面试题:线程是什么?多线程

    万次阅读 多人点赞 2018-10-27 10:52:48
    什么使用多线程?多线程的示例以及解决方案?线程池是什么? 一.线程是什么? 在Thread类中有这样的明确定义:线程是程序中执行的线程,Java虚拟机允许程序同时运行多个执行线程。 怎么创建一个线程呢? Thread中...
  • C++多线程并发(二)---线程同步之互斥锁

    万次阅读 多人点赞 2019-03-20 00:08:29
    在前一篇文章《C++多线程并发编程(一)—线程管理》中解释多线程并发时说到两个比较重要的概念: 多线程并发:在同一时间段内交替处理多个操作,线程切换时间片是很短的(一般为毫秒级),一个时间片多数时候...
  • Java 多线程同步和异步详解

    千次阅读 2018-05-31 10:00:32
    转载自 https://www.cnblogs.com/mengyuxin/p/5358364.htmljava线程 同步与异步 线程池1)多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线程的处理的数据,而B线程又修改了...
  • 一、多线程同步关键字-synchronized1.概念 synchronized保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。当多个并发线程访问同一个对象object中的同步...
  • 什么是进程? 当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的...什么多线程多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务, 也...
  • windows系统多线程同步机制原理总结

    千次阅读 2018-12-24 21:24:33
    windows系统多线程同步机制原理总结 同步问题是开发过程中遇到的重要问题之一。同步是要保证在并发执行的环境中各个控制流可以有序地执行,包括对于资源的共享或互斥访问,以及代码功能的逻辑顺序。 为了保证多线程...
  • C++11 多线程同步

    千次阅读 2016-11-09 21:28:05
    多线程能提高程序的效率,但同时也带来了相应的问题----数据竞争。当多个线程同时操作同一个变量时,就会出现数据竞争。出现数据竞争,一般会用临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、...
  • 多线程与并发 - 多线程访问同步方法的7种情况

    万次阅读 多人点赞 2021-09-14 22:16:28
    1、两个线程同时访问一个对象的同步方法 代码: public class SynchronizedObjectMethod implements Runnable { static SynchronizedObjectMethod instance = new SynchronizedObjectMethod(); @Override ...
  • 多线程:解释线程同步的必要性

    千次阅读 2020-08-25 17:12:44
    什么需要线程同步? 当线程同时运行时,线程的调度由操作系统决定,程序本身无法决定。因此,任何一个线程都有可能在任何指令处被操作系统暂停,然后在某个时间段后继续执行。 这个时候,有个单线程模型下不...
  • ConcurrentHashMap允许线程修改操作并发进行,其关键在于使用了锁分离技术。它使用了个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap 内部使用段(Segment)来表示这些不用的部分,每个段其实就是一...
  • Python多线程—线程同步

    千次阅读 2019-03-25 23:05:17
    线程同步的真实意思和字面意思恰好相反。 线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。 Python threading模块提供了Lock/RLock、Condition、queue...
  • Qt多线程同步

    千次阅读 2018-09-15 12:06:04
    一、Qt中使用多线程时候,多线程同步就是一个不可避免的问题。多线程同步就是使多个线程在同时执行同一段代码的时候,有顺序的执行,不会出现同时有两个或者多个线程执行同一段代码的情况,特别是在对变量或者...
  • 前言--前言是为了帮助大家能够更好的理解线程通信和线程同步,了解Java内存模型的抽象。 前言部分引用文章地址:...
  • 多线程同步和互斥有什么异同?

    千次阅读 2018-04-26 10:29:59
    点击打开原文链接线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。线程互斥是指对于共享的进程系统资源,在各单个...
  • c++实现多线程同步

    千次阅读 2018-05-23 16:01:27
    线程同步是指同一进程中的线程互相协调工作从而达到一致性。之所以需要线程同步,是因为线程同时对一个数据对象进行修改操作时,可能会对数据造成破坏,下面是线程同时修改同一数据造成破坏的例子: 1 #...
  • C++多线程并发(三)---线程同步之条件变量

    万次阅读 多人点赞 2019-05-03 12:43:12
    在前一篇文章《C++多线程并发编程(二)—线程同步之互斥锁》中解释了线程同步的原理和实现,使用互斥锁解决数据竞争访问问题,算是线程同步的加锁原语,用于排他性的访问共享数据。我们在使用mutex时,一般都会期望...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 830,359
精华内容 332,143
关键字:

同步多线程是什么意思