精华内容
下载资源
问答
  • 秒杀多线程第六篇 经典线程同步 事件Event

    万次阅读 多人点赞 2012-04-11 09:06:57
    阅读本篇之前推荐阅读以下姊妹篇:《秒杀多线程第四篇 一个经典线程同步问题》《秒杀多线程第五篇 经典线程同步关键段CS》 上一篇中使用关键段来解决经典线程同步互斥问题,由于关键段“线程所有权”特性...

    阅读本篇之前推荐阅读以下姊妹篇:

    秒杀多线程第四篇 一个经典的多线程同步问题

    秒杀多线程第五篇 经典线程同步关键段CS

     

    上一篇中使用关键段来解决经典的多线程同步互斥问题,由于关键段的“线程所有权”特性所以关键段只能用于线程的互斥而不能用于同步。本篇介绍用事件Event来尝试解决这个线程同步问题。

    首先介绍下如何使用事件。事件Event实际上是个内核对象,它的使用非常方便。下面列出一些常用的函数。

     

    第一个 CreateEvent

    函数功能:创建事件

    函数原型:

    HANDLECreateEvent(

     LPSECURITY_ATTRIBUTESlpEventAttributes,

     BOOLbManualReset,

     BOOLbInitialState,

     LPCTSTRlpName

    );

    函数说明:

    第一个参数表示安全控制,一般直接传入NULL

    第二个参数确定事件是手动置位还是自动置位,传入TRUE表示手动置位,传入FALSE表示自动置位。如果为自动置位,则对该事件调用WaitForSingleObject()后会自动调用ResetEvent()使事件变成未触发状态。打个小小比方,手动置位事件相当于教室门,教室门一旦打开(被触发),所以有人都可以进入直到老师去关上教室门(事件变成未触发)。自动置位事件就相当于医院里拍X光的房间门,门打开后只能进入一个人,这个人进去后会将门关上,其它人不能进入除非门重新被打开(事件重新被触发)。

    第三个参数表示事件的初始状态,传入TRUR表示已触发。

    第四个参数表示事件的名称,传入NULL表示匿名事件。

     

    第二个 OpenEvent

    函数功能:根据名称获得一个事件句柄。

    函数原型:

    HANDLEOpenEvent(

     DWORDdwDesiredAccess,

     BOOLbInheritHandle,

     LPCTSTRlpName     //名称

    );

    函数说明:

    第一个参数表示访问权限,对事件一般传入EVENT_ALL_ACCESS。详细解释可以查看MSDN文档。

    第二个参数表示事件句柄继承性,一般传入TRUE即可。

    第三个参数表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个事件。

     

    第三个SetEvent

    函数功能:触发事件

    函数原型:BOOLSetEvent(HANDLEhEvent);

    函数说明:每次触发后,必有一个或多个处于等待状态下的线程变成可调度状态。

     

    第四个ResetEvent

    函数功能:将事件设为末触发

    函数原型:BOOLResetEvent(HANDLEhEvent);

     

    最后一个事件的清理与销毁

    由于事件是内核对象,因此使用CloseHandle()就可以完成清理与销毁了。

     

    在经典多线程问题中设置一个事件和一个关键段。用事件处理主线程与子线程的同步,用关键段来处理各子线程间的互斥。详见代码:

    #include <stdio.h>
    #include <process.h>
    #include <windows.h>
    long g_nNum;
    unsigned int __stdcall Fun(void *pPM);
    const int THREAD_NUM = 10;
    //事件与关键段
    HANDLE  g_hThreadEvent;
    CRITICAL_SECTION g_csThreadCode;
    int main()
    {
    	printf("     经典线程同步 事件Event\n");
    	printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
    	//初始化事件和关键段 自动置位,初始无触发的匿名事件
    	g_hThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 
    	InitializeCriticalSection(&g_csThreadCode);
    
    	HANDLE  handle[THREAD_NUM];	
    	g_nNum = 0;
    	int i = 0;
    	while (i < THREAD_NUM) 
    	{
    		handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
    		WaitForSingleObject(g_hThreadEvent, INFINITE); //等待事件被触发
    		i++;
    	}
    	WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
    
    	//销毁事件和关键段
    	CloseHandle(g_hThreadEvent);
    	DeleteCriticalSection(&g_csThreadCode);
    	return 0;
    }
    unsigned int __stdcall Fun(void *pPM)
    {
    	int nThreadNum = *(int *)pPM; 
    	SetEvent(g_hThreadEvent); //触发事件
    	
    	Sleep(50);//some work should to do
    	
    	EnterCriticalSection(&g_csThreadCode);
    	g_nNum++;
    	Sleep(0);//some work should to do
    	printf("线程编号为%d  全局资源值为%d\n", nThreadNum, g_nNum); 
    	LeaveCriticalSection(&g_csThreadCode);
    	return 0;
    }

    运行结果如下图:

    可以看出来,经典线线程同步问题已经圆满的解决了——线程编号的输出没有重复,说明主线程与子线程达到了同步。全局资源的输出是递增的,说明各子线程已经互斥的访问和输出该全局资源。

     

    现在我们知道了如何使用事件,但学习就应该要深入的学习,何况微软给事件还提供了PulseEvent()函数,所以接下来再继续深挖下事件Event,看看它还有什么秘密没。

    先来看看这个函数的原形:

    第五个PulseEvent

    函数功能:将事件触发后立即将事件设置为未触发,相当于触发一个事件脉冲。

    函数原型:BOOLPulseEvent(HANDLEhEvent);

    函数说明:这是一个不常用的事件函数,此函数相当于SetEvent()后立即调用ResetEvent();此时情况可以分为两种:

    1.对于手动置位事件,所有正处于等待状态下线程都变成可调度状态。

    2.对于自动置位事件,所有正处于等待状态下线程只有一个变成可调度状态。

    此后事件是末触发的。该函数不稳定,因为无法预知在调用PulseEvent ()时哪些线程正处于等待状态

     

           下面对这个触发一个事件脉冲PulseEvent ()写一个例子,主线程启动7个子线程,其中有5个线程Sleep(10)后对一事件调用等待函数(称为快线程),另有2个线程Sleep(100)后也对该事件调用等待函数(称为慢线程)。主线程启动所有子线程后再Sleep(50)保证有5个快线程都正处于等待状态中。此时若主线程触发一个事件脉冲,那么对于手动置位事件,这5个线程都将顺利执行下去。对于自动置位事件,这5个线程中会有中一个顺利执行下去。而不论手动置位事件还是自动置位事件,那2个慢线程由于Sleep(100)所以会错过事件脉冲,因此慢线程都会进入等待状态而无法顺利执行下去。

    代码如下:

    //使用PluseEvent()函数
    #include <stdio.h>
    #include <conio.h>
    #include <process.h>
    #include <windows.h>
    HANDLE  g_hThreadEvent;
    //快线程
    unsigned int __stdcall FastThreadFun(void *pPM)
    {
    	Sleep(10); //用这个来保证各线程调用等待函数的次序有一定的随机性
    	printf("%s 启动\n", (PSTR)pPM);
    	WaitForSingleObject(g_hThreadEvent, INFINITE);
    	printf("%s 等到事件被触发 顺利结束\n", (PSTR)pPM);
    	return 0;
    }
    //慢线程
    unsigned int __stdcall SlowThreadFun(void *pPM)
    {
    	Sleep(100);
    	printf("%s 启动\n", (PSTR)pPM);
    	WaitForSingleObject(g_hThreadEvent, INFINITE);
    	printf("%s 等到事件被触发 顺利结束\n", (PSTR)pPM);
    	return 0;
    }
    int main()
    {
    	printf("  使用PluseEvent()函数\n");
    	printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
    
    	BOOL bManualReset = FALSE;
    	//创建事件 第二个参数手动置位TRUE,自动置位FALSE
    	g_hThreadEvent = CreateEvent(NULL, bManualReset, FALSE, NULL);
    	if (bManualReset == TRUE)
    		printf("当前使用手动置位事件\n");
    	else
    		printf("当前使用自动置位事件\n");
    
    	char szFastThreadName[5][30] = {"快线程1000", "快线程1001", "快线程1002", "快线程1003", "快线程1004"};
    	char szSlowThreadName[2][30] = {"慢线程196", "慢线程197"};
    
    	int i;
    	for (i = 0; i < 5; i++)
    		_beginthreadex(NULL, 0, FastThreadFun, szFastThreadName[i], 0, NULL);
    	for (i = 0; i < 2; i++)
    		_beginthreadex(NULL, 0, SlowThreadFun, szSlowThreadName[i], 0, NULL);
    	
    	Sleep(50); //保证快线程已经全部启动
    	printf("现在主线程触发一个事件脉冲 - PulseEvent()\n");
    	PulseEvent(g_hThreadEvent);//调用PulseEvent()就相当于同时调用下面二句
    	//SetEvent(g_hThreadEvent);
    	//ResetEvent(g_hThreadEvent);
    	
    	Sleep(3000); 
    	printf("时间到,主线程结束运行\n");
    	CloseHandle(g_hThreadEvent);
    	return 0;
    }

    自动置位事件,运行结果如下:

    手动置位事件,运行结果如下:

     

     

    最后总结下事件Event

    1.事件是内核对象,事件分为手动置位事件自动置位事件。事件Event内部它包含一个使用计数(所有内核对象都有),一个布尔值表示是手动置位事件还是自动置位事件,另一个布尔值用来表示事件有无触发。

    2.事件可以由SetEvent()来触发,由ResetEvent()来设成未触发。还可以由PulseEvent()来发出一个事件脉冲。

    3.事件可以解决线程间同步问题,因此也能解决互斥问题。

     

    后面二篇《秒杀多线程第七篇 经典线程同步 互斥量Mutex》和《秒杀多线程第八篇 经典线程同步 信号量Semaphore》将介绍如何使用互斥量和信号量来解决这个经典线程同步问题。欢迎大家继续秒杀多线程之旅。

     

    转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/7445233

    如果觉得本文对您有帮助,请点击支持一下,您的支持是我写作最大的动力,谢谢。



     

    展开全文
  • 多线程和多线程同步

    2020-03-02 17:27:04
    多线程情况下的共享资源问题,线程冲突,线程安全问题 创建自定义线程类的两种方式 class Thread类 Java中的一个线程类 Thread类是Runnable接口的实现类,同时提供了很多线程的操作使用的方法。 inte...

    多线程

    多线程的优缺点

    • 优点

      1. 提升资源利用率
      2. 提高用户体验
    • 缺点:

      1. 降低了其他线程的执行概率
      2. 用户会感受到软件的卡顿问题
      3. 增加的系统,资源压力
      4. 多线程情况下的共享资源问题,线程冲突,线程安全问题

    创建自定义线程类的两种方式

    • class Thread类

      • Java中的一个线程类
      • Thread类是Runnable接口的实现类,同时提供了很多线程的操作使用的方法。
    • interface Runnable接口

      • 这里规定了what will be run?
      • 里面只有一个方法 run方法
    • 方式一:

      • 自定义线程类,继承Thread类,重写run方法
      • 创建自定义线程对象,直接调用start方法,开启线程
    • 方式二:

      • 自定义线程类,遵从Runnable接口
      • 使用自定义遵从接口Runnable实现类对象,作为Thread构造方法参数
      • 借助于Thread类对象和start方法,开启线程

    【推荐】
    以上两种方式,推荐使用方拾二,遵从Runnable接口来完成自定义线程,不影响正常的继承逻辑,并且可以使用匿名内部类来完成线程代码块的书写。

    package com.qfedu.a_thread;
    
    /*
     * 自定义线程类MyThread1继承Thread类
     */
    class MyThread1 extends Thread {
    	@Override
    	public void run() {
    		for (int i = 0; i < 100; i++) {
    			System.out.println("继承Thread类自定义线程类");
    		}
    	}
    }
    
    /*
     * 自定义线程类MyThread2遵从Runnable接口
     */
    class MyThread2 implements Runnable {
    
    	@Override
    	public void run() {
    		for (int i = 0; i < 100; i++) {
    			System.out.println("遵从Runnable接口实现自定义线程类");
    		}
    	}
    }
    
    public class Demo1 {
    	public static void main(String[] args) {
    		new Thread(new Runnable() {
    
    			@Override
    			public void run() {
    				for (int i = 0; i < 100; i++) {
    					System.out.println("匿名内部类方式创建对象,作为线程执行代码");
    				}
    			}
    		}).start();
    		// 创建一个继承Thread类自定义线程类对象
    		MyThread1 myThread1 = new MyThread1();
    		// 这里不是启动线程,而且将run方法做出一个普通方法执行。
    		// myThread1.run();
    		myThread1.start();
    		
    		// 创建一个Thread类对象,使用遵从Runnable接口的实现类作为构造方法参数
    		Thread thread = new Thread(new MyThread2());
    		// 借助于Thread类内的start方法开启线程
    		thread.start();
    
    		for (int i = 0; i < 100; i++) {
    			System.out.println("main线程");
    		}
    	}
    }
    

    自定义线程执行流程简述

    在这里插入图片描述

    Thread类需要了解的方法

    • 构造方法 Constructor

      • Thread();
        分配一个新的线程对象,无目标,无指定名字
      • Thread(Runnable target);
        创建一个新的线程对象,并且在创建线程对象的过程中,使用Runnable接口的实现类
        对象作为执行的线程代码块目标
      • Thread(String name);
        创建一个新的线程,无指定目标,但是指定当前线程的名字是什么
      • Thread(Runnable target, String name);
        创建一个线程的线程对象,使用Runnable接口实现类对象,作为执行目标,并且指定
        name作为线程名
    • 成员方法:

      • void setName(String name);

      • String getName();
        以上两个是name属性setter和getter方法

      • void setPriority(int Priority);
        设置线程的优先级,非一定执行要求,只是增加执行的概率
        优先级数值范围 [1 - 10] 10最高 1最低 5默认

      • int getPriority();
        获取线程优先级

      • void start();
        启动线程对象

      • public static void sleep(int ms);
        当前方法是静态方法,通过Thread类调用,要求是当前所在线程代码块对应的线程,
        进行休眠操作,休眠指定的毫秒数

      • public static Thread currentThread();
        当前方法是静态方法,通过Thread类调用,获取当前所处代码块对应的线程对象。

    线程安全问题和解决方案

    线程安全问题–共享资源能使用问题

    <<湄公河行动>>
    100张票

    淘票票CGV 美团 猫眼
    三个销售渠道,100张票是一个共享资源!!!
    三个销售渠道,可以认为是三个销售线程!!!

    • 问题一:
      100张票共享资源问题,选什么来保存

      • 局部变量:
        在方法内,如果run方法执行,存在,run方法当前执行完毕,销毁。
        每一个线程对象中都有run方法,无法满足共享问题
      • 成员变量:
        每一个线程对象中,都有一个对应的成员变量,非共享资源。
      • 静态成员变量:
        属于类变量,所有的当前类对象,使用的静态成员变量都是一个,而且一处修改,处处
        受影响。【共享资源】
    • 问题二:
      资源冲突问题
      在这里插入图片描述

    同步代码块

    synchronized (/* 锁对象 */) {
        
    }
    
    /*
    特征:
     	1. synchronized 小括号里面的对象是锁对象,并且要求如果是多线程的情况下,锁对象必须是同一个对象。
     	2. synchronized 大括号中的代码块就是需要进行同步的代码,或者说是加锁的代码,大括号里面的内容,有且只允许一个线程进入。
     	3. 同步代码块越短越好,在保证安全的情况下,提高性能
     
    问题:
    	1. 目前锁对象感觉很随意,存在一定的隐患
    	2. 代码层级关系很复杂,看着有点麻烦
    */
    

    同步方法

    • synchronized 作为关键字来修饰方法,修饰的方法就是对应的同步方
      有且只允许一个线程进入,到底是谁来完成的加锁操作?
    1. 静态成员方法
      锁对象,是当前类对应的字节码文件.class 类名.class
    2. 非静态成员方法
      锁对象就是当前类对象 this
    • 选择同步方法是否使用static修饰问题
      1. 如果非static修饰,要保证执行的线程对象有且只有一个,因为锁对象就是当前线程对

      2. 如果是static修饰,锁对象具有唯一性,多个线程使用的锁是同一个锁。

    Lock锁

    • Java提供了一个对于线程安全问题,加锁操作相对于同步代码块和同步方法更加广泛的一种操作方式。
    1. 对象化操作。
      创建Lock构造方法
      Lock lock = new ReentrantLock();
    2. 方法化操作。
      开锁:
      unlock();
      加锁:
      lock();

    三种加锁方式的总结

    1. 一锁一线程,一锁多线程问题。
      使用对应的锁操作对应的线程,考虑静态和非静态问题。
      同步方法和Lock锁使用。
      静态是一锁多目标,非静态是一锁一目标

    2. 涉及到同步问题时,要考虑好锁对象的选择问题
      同步代码块,同步方法,Lock对象。

    守护线程

    • 守护线程,也称之为后台线程,如果当前主线程GG思密达,守护线程也就GG思密达。

    • 守护线程一般用于:

      1. 自动下载
      2. 操作日志
      3. 操作监控
    • 方法是通过线程对象

      • setDeamon(boolean flag);
      • true为守护线程
      • false缺省属性,正常线程

    线程状态

    六种线程状态

    • 线程有如果按照java.lang.Thread.State枚举方式来考虑,一共提供了6中状态。
    状态 导致状态的发生条件
    NEW(新建) 线程刚刚被创建,没有启动,没有调用start方法
    RUNNABLE(可运行) 线程已经可以在JVM中运行,但是是否运行不确定,看当前线程是否拥有CPU执行权
    BLOCKED(锁阻塞) 当前线程进入一个同步代码需要获取对应的锁对象,但是发现当前锁对象被其他线程持有,当前线程会进入一个BLOCKED。如果占用锁对象的线程打开锁对象,当前线程持有对应锁对象,进入Runnable状态
    WAITING(无限等待) 通过一个wait方法线程进入一个无限等待状态,这里需要另外一个线程进行唤醒操作。进入无限等待状态的线程是无法自己回到Runnable状态,需要其他线程通过notify或者notifyAll方法进行唤醒操作
    TIMED_WAITING(计时等待) 当前线程的确是等待状态,但是会在一定时间之后自动回到Runnable状态,例如 Thread.sleep() 或者是Object类内的wait(int ms);
    TERMINATED(被终止) 因为Run方法运行结束正常退出线程,或者说在运行的过程中因为出现异常导致当前线程GG思密达

    TIMED_WAITING(计时等待)

    • Thread.sleep(int ms);
      在对应线程代码块中,当前线程休眠指定的时间。

    • Object类内 wait(int ms);

      • 让当前线程进入一个计时等待状态
        1. 规定的时间及时完毕,线程回到可运行状态
        2. 在等待时间内,通过其他线程被notify或者notifyAll唤醒
    • Sleep方法

      1. 调用之后休眠指定时间
      2. sleep方法必须执行在run方法内,才可以休眠线程
      3. sleep不会打卡当前线程占用的锁对象。
        在这里插入图片描述

    BLOCKED(锁阻塞)

    • 线程中有锁存在,线程需要进入带有锁操作的同步代码,如果锁对象被别人持有,只能在锁外等待

    • 锁阻塞状态的线程是否能够抢到锁对象有很多因素

      1. 优先级问题,非决定因素
      2. CPU执行概率问题。
    • 后期高并发一定会存在多线程操作锁对象问题,秒杀,抢购…
      队列方式来处理
      在这里插入图片描述

    展开全文
  • 于是抽出三个小时时间吧windows所有关于多线程同步的方式代码重新敲了一遍。 有不对地方大家交流 ,转载请标明出处 http://blog.csdn.net/yue7603835/  以前用博客 好几年了 新手入门可以去看看 那里...

                     最近看了几个大公司的面试题基本都有多线程同步相关的知识 ,看完之后很惭愧啊! 多年没搞VC了基本上忘光了,,汗。于是抽出三个小时时间吧windows下所有关于多线程同步的方式代码重新敲了一遍。   

    有不对的地方大家交流  ,转载请标明出处   

    http://blog.csdn.net/yue7603835/  以前用的博客 好几年了   新手入门可以去看看 那里记录了好多学习经历。。。

      1、Interlock系列函数实现原子操作 
    // Study.cpp : 定义控制台应用程序的入口点。
    //
    /************************************************************************/
    /* 原子操作                                                                     */
    /************************************************************************/
    #include "stdafx.h"
    #include "windows.h"
    #include "process.h"
    //typedef  void( *ThreadProc )( void * ) ;    //定义 _beginthreadex的线程函数 
    uintptr_t __stdcall ThreadProc(void * param) ;//声明线程函数
    int currentCount=0 ;//当前的个数 
    int _tmain(int argc, _TCHAR* argv[])
    {  
    const int THREAD_NUM=50 ; //每次50个线程 
        int num=50 ;  //重复20次数

    while(num--){
    HANDLE  handleArr[THREAD_NUM] ;
    currentCount=0 ;
    for(int i=0;i<THREAD_NUM;i++){
    handleArr[i]=(HANDLE)_beginthreadex(NULL,0,ThreadProc,&i,0,NULL) ;//创建多个线程
    }
    ::WaitForMultipleObjects(THREAD_NUM,handleArr,TRUE,0xFFFFFFFF) ;//一直等待
    //顺便关闭内核对象的引用 。、这里不写了
    for(int i=0;i<THREAD_NUM;i++)
    CloseHandle(handleArr[i]) ;//减少对内核对象的引用计数
    printf("Current Count:%d\n",currentCount) ;//输出当前计数利用Interlock系列函数进行原子操作后就不会有问题了
    }
    return 0;
    }
    uintptr_t  __stdcall ThreadProc(void * param) {
    Sleep(10) ;
    //增加计数 
    //currentCount++ ;
    InterlockedIncrement((LPLONG)&currentCount) ;//原子操作保证了 一次只能访问一个变量
    Sleep(10);
    return 0 ;
    }
    2、关键代码段是实现互斥操作
     // Thread2.cpp : 定义控制台应用程序的入口点。
    //

    #include "stdafx.h"
    #include "windows.h"
    #include "process.h"
    /************************************************************************/
    /*  关键段只能实现 线程之间的互斥访问 不能实现同步
    *   因为只能由一个线程进入 并且自己退出关键段 并且不能跨线程进入和出去
    /************************************************************************/
    #define THREAD_NUM 10
    //线程的个数 
    int currentCount =0  ; 
    CRITICAL_SECTION  hSection ;  //关键代码段 
    unsigned int __stdcall ThreadProc(void * param){
    //由于创建线程是要一定的开销的,所以新线程并不能第一时间执行到这来 
    int index=*((int *)param) ;  
    Sleep(100) ;
    EnterCriticalSection(&hSection); //进入关键代码段 
    currentCount++ ; 
    printf("CurrentIndex:%d,CurrentCount:%d\n",index,currentCount) ;   //虽然不能保证  index是顺序走的但是能保证 被访问的代码是顺序无重复的 。..CriticalSection不能实现 线程间的同步 因为线程所有权的功能旋转锁
    LeaveCriticalSection(&hSection);//离开关键代码段
    Sleep(10) ;

    return  0 ;// 真正的返回是通过 返回值返回而不是 直接 endthreadex 
    }

    int _tmain(int argc, _TCHAR* argv[])
    {
        InitializeCriticalSection(&hSection) ; //初始化关键代码段 
    HANDLE handleArr[THREAD_NUM] ;
    for(int i=0;i<THREAD_NUM;i++){
       handleArr[i]=(HANDLE)_beginthreadex(NULL,0,ThreadProc,(LPLONG)&i,0,NULL); 
    }
    ::WaitForMultipleObjects(THREAD_NUM,handleArr,TRUE,0XFFFFFFFF)  ;
    DeleteCriticalSection(&hSection) ;//删除代码段
    for(int i=0;i<THREAD_NUM;i++){
    CloseHandle(handleArr[i]);//减少内核对象引用计数 
    }
    return 0;
    }
    3、事件对象+关键代码段实现 主线程和子线程的同步 子线程和子线程的互斥 
     // Thread2.cpp : 定义控制台应用程序的入口点。
    //

    #include "stdafx.h"
    #include "windows.h"
    #include "process.h"
    /************************************************************************/
    /*  关键段只能实现 线程之间的互斥访问 不能实现同步
    *   因为只能由一个线程进入 并且自己退出关键段 并且不能跨线程进入和出去
    /************************************************************************/
    #define THREAD_NUM 10
    //线程的个数 
    int currentCount =0  ; 
    CRITICAL_SECTION  hSection ;  //关键代码段 
    unsigned int __stdcall ThreadProc(void * param){
    //由于创建线程是要一定的开销的,所以新线程并不能第一时间执行到这来 
    int index=*((int *)param) ;  
    Sleep(100) ;
    EnterCriticalSection(&hSection); //进入关键代码段 
    currentCount++ ; 
    printf("CurrentIndex:%d,CurrentCount:%d\n",index,currentCount) ;   //虽然不能保证  index是顺序走的但是能保证 被访问的代码是顺序无重复的 。..CriticalSection不能实现 线程间的同步 因为线程所有权的功能旋转锁
    LeaveCriticalSection(&hSection);//离开关键代码段
    Sleep(10) ;

    return  0 ;// 真正的返回是通过 返回值返回而不是 直接 endthreadex 
    }

    int _tmain(int argc, _TCHAR* argv[])
    {
        InitializeCriticalSection(&hSection) ; //初始化关键代码段 
    HANDLE handleArr[THREAD_NUM] ;
    for(int i=0;i<THREAD_NUM;i++){
       handleArr[i]=(HANDLE)_beginthreadex(NULL,0,ThreadProc,(LPLONG)&i,0,NULL); 
    }
    ::WaitForMultipleObjects(THREAD_NUM,handleArr,TRUE,0XFFFFFFFF)  ;
    DeleteCriticalSection(&hSection) ;//删除代码段
    for(int i=0;i<THREAD_NUM;i++){
    CloseHandle(handleArr[i]);//减少内核对象引用计数 
    }
    return 0;
    }
    4、单独用事件实现主线程和子线程的同步 子线程和子线程的互斥
    // Thread3.cpp : 定义控制台应用程序的入口点。
    //
    /************************************************************************/
    /*事件既可以解决线程同步问题 也可以解决互斥问题  完全同步
    PulseEvent () 事件脉冲可以同时使多个等待线程变为可调度状态 但并不保证所有线程都能变为可调度 状态
    脉冲事MS不支持使用 应该使用Condition来实现
    事件是内核对象,事件分为手动置位事件和自动置位事件。事件Event内部它包含一个使用计数(所有内核对象都有),
    一个布尔值表示是手动置位事件还是自动置位事件,另一个布尔值用来表示事件有无触发。
    事件可以由SetEvent()来触发,由ResetEvent()来设成未触发。还可以由PulseEvent()来发出一个事件脉冲。
    事件可以解决线程间同步问题,因此也能解决互斥问题。
    互斥量是内核对象,它与关键段都有“线程所有权”所以不能用于线程的同步。
    互斥量能够用于多个进程之间线程互斥问题,并且能完美的解决某进程意外终止所造成的“遗弃”问题。 比如一个进程在创建了
    xxx的互斥量  并且获得所有权这时候另一个 进程 是不能通过OpenMutex(MUTEX_ALL_ACCESS, TRUE, MUTEX_NAME); //打开互斥量的
    如果拥有互斥量的进程 ExitThread 意外终止那么 这个时候系统就会释放该互斥量的占用 
    另一个进程就可以打开互斥量 并且 OpenMutex 返回 WAIT_ABANDONED
    /************************************************************************/
    #include "stdafx.h"
    #include "windows.h"
    #include "process.h"
    #define THREAD_NUM 100 //产生的线程的数目
    HANDLE  hEvent1 ;  //用于父子线程之间的同步
    HANDLE  hEvent2 ;//用于子线程之间的互斥
    int currentCount=0  ;  //线程共享全局变量 
    unsigned int __stdcall ThreadProc(void * param){
    Sleep(10) ;//暂停当前线程然能执行的线程都能分配到时间片如果不加 会出现最后并没有执行100个线程的样子 放弃自己的时间片
    int index=*(int*)param ; 
    SetEvent(hEvent1) ;//激活事件对象这个时候 主线程才继续走
    WaitForSingleObject(hEvent2,0xFFFFFFFF) ;//等待事件被激活  
    ResetEvent(hEvent2);//事件没有信号
    currentCount++ ;//递增 
    printf("CurrentIndex:%d,CurrentCount:%d\n",index,currentCount) ;
    SetEvent(hEvent2);//事件有信号
    return 0 ;
    }
    int _tmain(int argc, _TCHAR* argv[])
    {   
    hEvent1=CreateEvent(NULL,FALSE,FALSE,NULL); //创建 手动重置 初始化未被激活的事件对象
    hEvent2=CreateEvent(NULL,FALSE,FALSE,NULL);//创建冲动重置 未激活 的事件对象
    HANDLE handleArr[THREAD_NUM] ;
    int  i;
    SetEvent(hEvent2);//激活事件对象2用于线程之间的互斥
    for(i=0;i<THREAD_NUM;i++){
    handleArr[i]=(HANDLE)_beginthreadex(NULL,0,ThreadProc,(LPLONG)&i,0,NULL) ;
    ::WaitForSingleObject(hEvent1,0xFFFFFFFF) ;//等待 事件对象被激活 
    //printf("主线程循环了%d次\n",i);
    // Sleep(1) ;
    }
    ::WaitForMultipleObjects(THREAD_NUM,handleArr,TRUE,INFINITE);
    printf("Current i:%d\n",i);
    for(i=0;i<THREAD_NUM;i++)
    CloseHandle(handleArr[i]) ;//减少内核对象引用计数
    CloseHandle(hEvent1) ;
    CloseHandle(hEvent2) ;
    return 0;
    }

    5、互斥量+实现实现 主线程和子线程同步 子线程和子线程互斥
     // Thread5.cpp : 定义控制台应用程序的入口点。
    /************************************************************************/
    /*  Mutex互斥量是一个内核对象,它用来确保一个线程独占一个资源的访问。互斥量与CriticalSection的类似,
        并且互斥量可以用于不同进程中的线程互斥访问资源,比如一个应用程序只允许有一个实例运行。
    WaitForSingleObject的Mutex如果在同一个线程,系统会把当前线程置为可调度的。  和CriticalSection一样 
    拥有线程所有权,可以重复的
    在多线程中只能作为互斥使用  
    事件 和互斥对象实现 主线程和子线程的同步 子线程之间的互斥
    /************************************************************************/
    #include "stdafx.h"
    #include "windows.h"
    #include "process.h"
    #define THREAD_NUM 100 
    HANDLE hMutex ;
    HANDLE hEvent ; 
    int currentCount=0 ;
    unsigned int __stdcall ThreadProc(void * param){

    Sleep(50) ;//暂时放弃时间片保证所有线程都能初始化 不被遗弃  
    int index=*(int*)param ; 
    WaitForSingleObject(hMutex,INFINITE) ;//等待
    SetEvent(hEvent);//激活信号

    //下面是子线程之间 互斥操作
    currentCount++ ; 
    printf("CurrentIndex:%d,CurrentCount:%d\n",index,currentCount) ;
    ReleaseMutex(hMutex) ;//释放互斥对象 其他线程变为可调度状态

    return 0 ;
    }
    int _tmain(int argc, _TCHAR* argv[])
    {  
    HANDLE handleArr[THREAD_NUM];
    hMutex=CreateMutex(NULL,FALSE,NULL) ;//表示不被当前线程所拥有 那么说明 Mutex处于激活状态
    hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);//手动重置的事件对象 初始化没有信号  
    for (int i=0;i<THREAD_NUM;i++)
    {
    handleArr[i]=(HANDLE)_beginthreadex(NULL,0,ThreadProc,&i,0,NULL) ;  
    WaitForSingleObject(hEvent,0xFFFFFFFF);
    }
    WaitForMultipleObjects(THREAD_NUM,handleArr,TRUE,0xFFFFFFFF) ;
    for (int i=0;i<THREAD_NUM;i++){
    CloseHandle(handleArr[i]) ;//减少内核对象引用
    }
    CloseHandle(hMutex) ;
    CloseHandle(hEvent) ;
    return 0;
    }
    6、信号量Semaphore实现主线程和子线程同步 子线程和子线程互斥 
     // Thread6.cpp : 定义控制台应用程序的入口点。
    /************************************************************************/
    /* 信号量实现互斥和同步 
    HANDLE WINAPI CreateSemaphore(
    __in_opt  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
    __in      LONG lInitialCount,
    __in      LONG lMaximumCount,
    __in_opt  LPCTSTR lpName
    );
    BOOL WINAPI ReleaseSemaphore(
    __in       HANDLE hSemaphore,
    __in       LONG lReleaseCount,
    __out_opt  LPLONG lpPreviousCount
    );
    /************************************************************************/
    #include "stdafx.h"
    #include "windows.h"
    #include "process.h"
    #define THREAD_NUM 100 
    HANDLE  hSemaphore1  ; //主线程和子线程同步 
    HANDLE  hSemaphore2  ;//子线程和子线程互斥 
    int currentCount =0 ;
    unsigned int __stdcall  ThreadProc(void *parma){

    Sleep(20);//依然是放弃时间片 保证线程都能正确运行
    int index=*(int*)parma ;
    WaitForSingleObject(hSemaphore2,0xFFFFFFFF) ;//减少信号量 放在此处能保证在其他子线程之前 获取单一信号量
    ReleaseSemaphore(hSemaphore1,1,NULL) ;//信号量增加

        currentCount++ ;
    printf("CurrentIndex:%d,CurrentCount:%d\n",index,currentCount);
    ReleaseSemaphore(hSemaphore2,1,NULL) ;


    return 0 ;
    }
    int _tmain(int argc, _TCHAR* argv[])
    {   
    HANDLE handleArr[THREAD_NUM] ;
    hSemaphore1=CreateSemaphore(NULL,0,1,NULL); //0个资源 并发线程1 
    hSemaphore1=CreateSemaphore(NULL,0,1,NULL);  
    ReleaseSemaphore(hSemaphore2,1,NULL) ;//子线程之间的互斥使用
    for (int i=0;i<THREAD_NUM;i++)
    {
    handleArr[i]=(HANDLE)_beginthreadex(NULL,0,ThreadProc,&i,0,NULL) ; 
    WaitForSingleObject(hSemaphore1,0xFFFFFFFF) ;  //如果信号量资源大于0会减少信号量 
    }
    WaitForMultipleObjects(THREAD_NUM,handleArr,TRUE,0xFFFFFFFF) ;
    for(int i=0;i<THREAD_NUM;i++)
    CloseHandle(handleArr[i]) ;
    CloseHandle(hSemaphore1);
    CloseHandle(hSemaphore2);
    return 0;
    }

    展开全文
  • 多线程同步的方法

    2017-04-25 21:48:21
    * 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行过程中CPU不要切换到其他线程工作. 这时就需要同步. * 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外...

    一,概述

    1,什么是线程同步?

    当使用多个线程来访问同一个数据时,这个数据在被一个线程访问完成前不允许被其他线程访问。这就叫同步。

    2,什么情况下需要同步?

    * 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
    * 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
    

    3,多线程同步的方法

    多线程同步的方法一共有五个:
    1. 使用synchronized 关键字构成同步方法。
    2. 使用synchronized 关键字构成同步代码块。
    3. 使用ReentrantLock类构成同步代码块。
    4. 使用volatile关键字修饰变量。
    5. 如果使用ThreadLocal对象管理变量。

    以上5种方法中使用最多的是前三种,第5种在Handler机制中有使用,第4种很少使用。下面详细讲述前三种方法。

    二,不使用同步时出现的问题

    下面写一个demo,模拟线程不同步带来的问题。
    首先定义一个成员变量和一个成员方法,如下:

        public int num = 100;
        public  void print(){
            if(num >0){
                try {
                    Thread.sleep(1000);//sleep(1000)可以让结果更清晰
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                num--;
                Log.d(TAG,"num==============="+num);
            }
        }

    代码很简单,这里不做解释。

    然后在点击事件中开启两个子线程,都调用print()方法,代码如下:

        public void click(View v){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        print();
                    }
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        print();
                    }
                }
            }).start();
        }

    我们期望的结果中不能出现num=0的情况,而在实际情况中有很大的概率出现了num=0。

    问题原因分析:
    当有多个线程并发时,哪个线程被执行是随机的。假设此时num=1,线程1抢到了执行权,线程1可以进入到if代码块中,当线程1正在执行sleep(1000)时执行权被线程2抢走了,而此时num仍然等于1,所以线程2也可以进入if代码块中,线程2中代码完全执行,此时num就等于0。此时线程1又获取到了执行权,由于线程1已经进入了if代码块中,此时会继续向下执行num–,所以就出现了num等于0的情况。

    显然这是一个严重的问题,假如这是一个卖票系统,当票数等于0时还在卖票,就会出现问题。所以我们必须解决解决办法就是使用同步方法。

    三, 使用synchronized 关键字构成同步方法。

    改写后的print方法如下:

        public synchronized void print(){//使用synchronized 关键字修饰方法
            if(num >0){
                try {
                    Thread.sleep(1000);//sleep(1000)可以让结果更清晰
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                num--;
                Log.d(TAG,"num==============="+num);
            }
        }

    此时再次调用print方法就不会出现num=0的情况。
    解析:当方法使用synchronized 关键字修饰后,这个方法称为同步方法。当一个线程调用同步方法时,只有这个方法执行完毕后才会释放执行权。此时就实现了同步的作用。

    四,使用synchronized 关键字构成同步代码块

    改写后的print方法如下:

        public  void print(){
            synchronized (this) {//添加同步锁
                if (num > 0) {
                    try {
                        Thread.sleep(1000);//sleep(1000)可以让结果更清晰
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    num--;
                    Log.d(TAG, "num===============" + num);
                }
            }
        }

    此时再次调用print方法也不会出现num=0的情况。
    解析:此时的方法虽然不是同步方法,但被synchronized 关键字包裹的代码块称为同步代码块。当一个线程调用方法执行到同步代码块时,只有同步代码块执行完毕后才会释放执行权。此时就实现了同步的作用。

    五,使用ReentrantLock类构成同步代码块

    ReentrantLock是java5.0后提供的一个类,它有两个重要的方法lock和unlock,使用这两个方法也可以给代码块加锁。使用示例如下:

    private ReentrantLock lock = new ReentrantLock();
        public void print(){
            lock.lock();//加锁
                if (num > 0) {
                    try {
                        Thread.sleep(1000);//sleep(1000)可以让结果更清晰
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    num--;
                    Log.d(TAG, "num===============" + num);
                }
            lock.unlock();//解锁
        }

    此时再次调用print方法也不会出现num=0的情况。
    解析:ReentrantLock 类具有锁机制,当调用lock方法时这个线程就不会使用执行器,只有遇到unlock时才有可能失去执行权,所以这种方法也可以实现线程的同步。
    注:当遇到unlock时这个线程才有可能失去执行权,但不一定会失去,有可能会继续拥有执行权。

    六,Synchronized与ReentrantLock的区别

    Synchronized是java中提供的一个关键字。ReentrantLock是java5.0后提供的一个类,这个类是java.util.concurrent.locks包中的,具有lock和unlock两个重要的方法。

    二者在性能上的区别是:
    1,Synchronized可能出现死锁现象。
    2,ReentrantLock具有lockInterruptibly方法,可以中断锁等候。若线程一获得了锁,线程1执行的内容又比较多,此时若线程2不想等待,就可以中断锁,可以优化性能。而且不会出现死锁现象。在线程间通讯时ReentrantLock提供了Condition实例对象,让等待唤醒机制操作更加灵活。

    七,线程安全

    1,什么是线程安全?

    线程安全是指:同一个数据在某一个时间段只能被一个线程操作。或者说一个数据被一个线程访问结束后才能被其他线程访问。

    2,线程安全与线程同步的区别?

    线程同步是一种技术方法。
    线程安全是一种实现的效果。
    使用了线程同步保证了线程安全。

    3,常见的线程安全的例子

    • Vector是线程安全的,ArrayList是线程不安全的
    • StringBuffer是线程安全的,StringBuilder是线程不安全的
    • Hashtable是线程安全的,HashMap是线程不安全的
    展开全文
  • windows下多线程同步

    2018-05-04 18:26:44
    互斥量(锁)适用范围:可以跨进程同步,还可以用来保证程序只有一个实例运行(创建命名互斥量),也可以用来做线程的同步#include&lt;Windows.h&gt; #include&lt;iostream&gt; using namespace ...
  • 多线程的同步

    2018-05-08 21:28:23
    多线程的环境中, 经常会遇到数据共享问题, 即当多个线程需要访问同一资源时, 他们需要以某种顺序来确保该资源在某一时刻只能被一个线程使用,否则, 程序运行结果将会是不可预料, 在这种情况, 就必须...
  • 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时需要线程同步线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池,等待前面的线程使用完毕,一个...
  • 今天学习一下Linux下多线程的开发,这在Android开发中很常见,也学习一下线程同步的实现。第一步就来创建线程实现  #include #include #include #include #include #include #include #include #include ...
  • 多线程的同步

    2017-08-29 23:42:28
    多线程编程本质 并发性是多线程编程本质 在宏观上,所有线程并行执行 多个线程间相对独立,互不干涉 同步:在特殊情况,控制多线程相对执行顺序QThread类直接支持线程间的同步
  • 多线程:解释线程同步的必要性

    千次阅读 2020-08-25 17:12:44
    为什么需要线程同步? 当个线程同时运行时,线程调度由操作系统决定,程序本身无法决定。因此,任何一个线程都有可能在任何指令处被操作系统暂停,然后在某个时间段后继续执行。 这个时候,有个单线程模型不...
  • 秒杀多线程第五篇 经典线程同步 关键段CS

    万次阅读 多人点赞 2012-04-11 09:06:40
    上一篇《秒杀多线程第四篇 一个经典线程同步问题》提出了一个经典线程同步互斥问题,本篇将用关键段CRITICAL_SECTION来尝试解决这个问题。本文首先介绍如何使用关键段,然后再深层次分析关键段实现...
  • linux 下的多线程同步

    2009-12-24 08:48:00
    1 引言 线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面佼佼者。传统Unix也支持线程概念,但是在一个进程(process)中只允许有一个线程,这样...
  • java多线程的同步机制

    2021-02-14 01:04:28
    线程同步其实就是一种等待机制,个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,一个线程再使用。 2.为什么有线程同步机制(目的)? 答: 解决线程安全问题 3.什么是线程安全问题...
  • Day 21 多线程和多线程同步

    热门讨论 2020-03-02 16:54:17
    多线程情况下的共享资源问题,线程冲突,线程安全问题 1.2 创建自定义线程类的两种方式 class Thread类 Java中的线程类 Runnable接口的实现类,同时提供了很多线程的操作使用方法 interface Run...
  • 互斥量控制每次只有一个线程获得互斥量,执行操作,其他调用lock的线程都会阻塞;互斥量适合一个进程内的多线程访问公共区域或代码段时使用。
  • 线程同步没控制好,直接查询数据库表tokenID,在同机构线程并发时,运行时会产生误读,导致重复插入。 关于重复问题, 让他们把查询token直接改成update 表名 Set token=开始拆分 where token=初始未拆分状态...
  • 线程同步其实就是一种等待机制,个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,一个线程再使用。 锁机制:由于同一进程的个线程共享同一块存储空间,在带来方便的同时,也...
  • 多线程同步

    2017-06-30 11:28:44
    读陈硕先生《moduo多线程服务器编程》第二章线程同步精要,做笔记。编程概要 首要原则是尽量最低限度共享对象,减少需要同步场合。 其次是使用高级并发编程组件,如TaskQueue、Producer-Consumer Queue、...
  • 进程: 1.是资源分配的最小单位,是CPU进行分配和调度的单位。...3.线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步
  • Qt多线程同步的总结

    2020-05-28 16:41:08
    方便程序随时调用,生命周期是程序开始到结束,我是用数组进行存储的QVector ,在多线程的情况,会有生成图片数据的线程还有修改针点数据的线程,为确保数据使用时的一致性,需要线程同步。了解到的方法有 1、...
  • WindowsC++多线程同步

    2016-09-06 11:14:40
    程序是计算机指令集合,它以文件形式存储在...当多线程访问全局变量时需要多线程同步:互斥对象、事件对象、关键代码段 互斥对象:互斥独享可以看做是一把房间钥匙,只有得到这把钥匙后,我们才能进入放进啊,完
  • Linux下的线程同步编程实例 交通数据对接平台,需要linux终端在向城道平台发送数据的同时,隔一段时间就需要与城道平台的服务器进行校时,保持时间同步。因而需要多线程同步编程,这里顺便把之前的一个多线程入门...
  • 前言 在前一篇文章Java 多线程(4)—线程同步(中) ...这篇是线程同步文章最后一篇,我们来一下一些新知识点: volatile 关键字 首先我们来看一下 Java 中另一个和多线程有关关键字: vola...
  • 本文主要是讲述 IOS 多线程线程同步的三种方式,更多IOS技术知识,请登陆疯狂软件教育官网。  一般情况我们使用线程,在多个线程共同访问同一块资源。为保护线程资源安全和线程访问正确性。  在IOS中...
  • 一、什么叫做线程的同步与互斥?为什么需要同步与互斥? 二、互斥量 三、条件变量 四、信号量 五、读写锁
  • 接触线程很久了,一直就只知道多线程并发中很有可能引起线程安全问题,所以啊就需要进行线程同步,就是通过什么synchronize,Lock,volatile,JUC并发包下的那几个类,ThrealLocal,AQS,原子变量和阻塞队列啊等等...
  • Java多线程线程同步

    2016-07-03 21:13:25
    线程同步线程安全问题多线程情况,当多个线程访问同一个数据时,很容易出现线程安全问题。经典问题——银行取钱问题。几个人同时取一个帐号里钱,就可能出现问题。下面模拟一下。//账户类 public class ...
  • 在Java的学习中必然会遇到多线程同步问题,对于初学者一般只会理解到相关api的使用,但是很少能够真正知道为什么要对多线程环境下的对象进行同步,接下来我就谈谈自己的一些理解。 JVM堆与JVM栈宏观室是分离的,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 13,786
精华内容 5,514
关键字:

多线程下的线程同步