20160226.CCPP体系详解(0036天)

Posted 尹成

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了20160226.CCPP体系详解(0036天)相关的知识,希望对你有一定的参考价值。

程序片段(01):01.多线程.c+02.多线程操作.c
内容概要:多线程

///01.多线程.c
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <process.h>

//01.线程任务函数剖析:
//  1."封装"线程任务代码
//  2.MessageBox();作用:
//      用于"阻塞"当前线程的继续执行状态
//      也就是暂停线程任务代码的执行
//02.MessageBox();带参宏的作用:
//  1.弹出一个消息盒子
//  2.该带参宏位于那条线程任务函数当中,就会在
//      该线程之上产生同步效果,从而阻塞该条线程
//注:MessageBox();带参宏和TEXT();带参宏结合可以
//  解决字符集兼容性问题(让任何文字自动使用本地字符集)
void thTaskFun(void * pArr)
{
    MessageBox(0, TEXT("1"), TEXT("1"), 0);
}

//03.关于开启线程函数(_beginthread();)函数的解析:
//  1.头文件:process.h
//  2.格式:_beginthread(arg1, arg2, arg3);
//  3.参数:
//      arg1:函数指针(表象)<->线程任务代码(实质)
//      arg2:栈的尺寸,0表示默认情况(拷贝主线程栈尺寸作为新开启线程所占用的栈尺寸)
//      arg3:开启线程之后,向线程任务函数所传递的参数(数据封装体的地址)
//注:数据封装体的地址,便于跨函数访问多个参数!+提升效率
int main01(void)
{
    MessageBox(0, "2", "2", 0);//此处将会阻塞主线程

    _beginthread(thTaskFun, 0, NULL);//用于开启一条异步执行的线程
    _beginthread(thTaskFun, 0, NULL);
    _beginthread(thTaskFun, 0, NULL);
    _beginthread(thTaskFun,  0, NULL);

    system("pause");
}
///02.多线程操作.c
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <process.h>

//01.线程状态管理:
//  线程状态:冻结(暂停)+解冻(继续)
//注:_beginthread();不太注重代码格式要求,CreateThread();注重代码格式要求
void print(void * p)
{
    int i = 0;
    while (1)
    {
        printf("%d \n", ++i);
        Sleep(1000);
    }
}

//02.阻塞线程和阻塞进程:
//  阻塞线程
//      1.阻塞线程之后,只是当前线程的后续代码暂停执行状态
//      2.任何情况之下都可以触发阻塞线程的状态
// 阻塞进程:
//      1.阻塞进程之后,进程之内的所有代码全部处于暂停执行执行状态
//      2.CPU在为单个进程只开启单条线程的情况之下才能出现阻塞进程现象
//注:Debug调试模式状态下,无论是单线程还是多线程,只要阻塞了主线程
//      就会出现阻塞进程的现象,从而其它线程无法得到具体的执行
//注:只有在多线程的情况之下才具备阻塞线程和阻塞进程之间的区别
//      因为在单线程的情况之下阻塞线程就等同于阻塞进程
//注:阻塞主线程的情况之下,才有可能阻塞进程,从而让其它所有的代码得不到
//      继续执行的机会(当然,是在其他线程没有启动的情况之下)
//注:冻结主线程的情况之下,在调试模式下等同于阻塞进程,在这种情况之下可以
//      方便与实施对其他线程的状态切换操作
//03.阻塞线程的两种常见方式:
//      MessageBox();带参宏
//      system("pause");
int main02(void)
{
    _beginthread(print, 0, NULL);

    system("pause");//(Debug调试模式下)暂停主线程等同于暂停整个进程,便于对其它线程的状态切换操作

    system("pause");

    system("pause");

    system("pause");
}



//01.线程状态:冻结(暂停)&解冻(继续)
//02.线程休眠:Sleep();用于当前线程任务函数之中,休眠当前线程
//03.异步线程:_beginthread(run,0,NULL);
//  run:函数指针
//      0:堆栈尺寸
//  NULL:参数列表
//04.阻塞线程:
//  MessageBox();
//  system("pause");
//05.冻结&解冻[线程管理方面的重要概念]
//  实质:在于是否存在于系统所控制的环形队列当中<=>是否处于等待CPU执行的状态
//  特点:由于CPu存在的固定的CPU执行频率,因此CPU对环形队列当中的线程任务分配
//      相应所需的时间片,按照顺序进行环形队列当中的线程任务函数内容切换
//注:一条线程对应于一个环形队列,对应于一组线程任务序列
//06.操作系统管理线程的特点:
//  1.操作系统为每个程序的相应代码片段分配相应的时间片
//  2.该时间片模型的本质就是一个环形队列(线程<->队列)
//  3.CPU针对于每一个时间片进行切换访问
//07.关于同步与异步的概念:
//  1.单条线程根本不存在同步与异步的概念,只是会出现同步与异步的现象
//  2.只有多线程的情况之下,才存在同步与异步的概念
//注:严格区分概念和现象
//08.如何进行线程冻结&解冻状态的演示?
//  1.采用两条线程
//  2.主线程的阻塞状态会导致副线程暂时无法获得CPU的执行权
//  3.在主线程的阻塞状态之下进行副线程的线程状态管理演示[冻结&解冻]
//09.测试多线程:多线程的常用操作
//  例子-->细节-->通信-->队列
//10.操作系统特点:
//  1.操作系统:PC&Phone
//  2.由于CPU存在CPU执行频率,所以操作系统分配给线程的任务代码时间片大小有限,
//      也就是环形队列总时间片固定,只不过是循环进行执行,所以应用程序不会存在执
//      行不到的情况[CPU刷新环形队列的频率]
//  3.单核CPU一个时刻只会执行一条线程,根据概率进行切换
//  4.关于多线程的假象问题:
//      单核CPU只能出现多线程的假象情况,不会出现多多线程的真相情况
//      也就是说某一时刻,单核CPU只会处理一条线程,多核CPU才能出现处理多线程情况
//  5.操作系统通过线程环形队列实现对CPU随机切换动作的调度[随机性]
//  6.所有的线程都放置在同一个操作系统所管理的环形队列当中,然后让操作系统对CPU进行随机调度执行
//      CPU调度执行某条线程-->从而指向某条线程的任务代码
//11.冻结&解冻:
//  冻结:休眠&从环形队列当中退出
//  解冻:运行&进入到环形队列当中-->等待执行(不一定立即得到CPU的执行权)
//  执行权:CPU切换到线程的执行状态-->执行

程序片段(02):01._beginthread.c+02.CreateThread.c
内容概要:多线程冲突问题

///01._beginthread.c
#include <stdio.h> 
#include <stdlib.h>
#include <Windows.h>
#include <time.h>
#include <process.h>

//01.临界区:CRITICAL_SECTION
//  1.解决多线程并发访问冲突问题
//  2.所允许的多线程并发访问数64
//  3.通常情况之下需要将临界区定义为全局变量:
//      为的是让多条线程识别同一个临界区!(防止互锁)
//注:临界区情况之下,如果并发访问数目大于64
//  那么多线程并发访问效果将会失效
//注:临界区的实质是一个结构体类型,它位于Windows.h
//  头文件中进行的类型定义
//注:C语言的全局变量不会进行自动初始化操作,CPP语言
//      会进行默认的自动初始化操作
static CRITICAL_SECTION cs;

//02.全局变量:用于多线程之间的通信信息描述
//  任何一个当前进程下的线程都可以访问这个全局变量
//注:全局变量在默认的情况之下,如果被多条线程所并发
//  访问,容易导致线程并发访问冲突问题!
static int num = 0;

//03.多线程并发访问问题详细剖析:
//  1.理论上:100条线程并发计算同一个数据之后的结果应当是
//      100*100=10000,但是结果却是达不到10000
//  2.原因是:多条线程同时读取到相同的数据,再进行数据写入
//      的时候,写入相同的结果,因此导致计算次数-1,因此结果-1
//注:使用临界区之后,多条线程在同一时刻的情况之下,只能由
//      单条线程进行数据的访问(读取和写入)
//注:使用临界区之后,将会有N多条线程处于运行状态下:
//  其中,(N-1)条处于运行"等待"状态下-->
//      全部都是处于"急切"等待状态下-->
//      临界区一旦接触,就会随机抽取一条线程得到CPU的执行权
//  其中,1条处于运行"执行"状态下
//注:避免频繁的使用临界区可以加快数据的访问效率
//  严格避免频发的加解锁情况发生(多线程效率提升与优化)
static void myFun(void * p)
{
    for (int i = 0; i < 100; ++i)
    {
        EnterCriticalSection(&cs);//进入临界区
        ++num;
        LeaveCriticalSection(&cs);//离开临界区
        Sleep(3);//不要讲时间操作放置于临界区当中(否则极度耗费时间)
    }
}

//04.如何对程序进行计时操作?
//  1.时间刻度
//  2.记录开始
//  3.记录结束
//  4.时间差值
//05.HANDLE类型解析:
//  1.位于Windows.h头文件当中
//  2.名为句柄,实质void *,也就是一个空类型的地址类型
//      只是一个地址数值的类型定义,没有明确的解析方式
//06.要想模拟多线程处理操作,必须等待所有线程执行完毕
//      最终再进行多条线程的结果处理
//07.等待多条线程执行状态的结束:
//  格式:WaitForMutipleObjects(arg1, arg2, arg3, arg4);
//  参数:arg1:线程个数+arg2:线程数组+arg3:单个|全部等待+arg4:等待时间
//08.这儿处在两个冲突问题:
//  1.同步执行:结果精确,耗费时间
//  2.异步执行:时间精确,结果不正确
//09.解决方式:
//  临界区:实质就是加解锁的特点
//      添加一把锁,必须等待当前线程执行状态完毕之后,再进行解锁,再让下一个
//      线程执行临界区当中的代码块儿
//  信号量:通过信号提示决定是否可以进行变量的访问
int main01(void)
{
    InitializeCriticalSection(&cs);
    time_t start, end;
    time(&start);
    HANDLE thArr[100] = { 0 };
    for (int i = 0; i < 100; ++i)
    {//这里需要等待所有线程执行结束并退出,所以需要使用线程句柄数组进行统一管理
        thArr[i] = _beginthread(myFun, 0, NULL);
        //thArr[i] = CreateThread(NULL, 0, myFun, NULL, 0, NULL);
        //这儿如果是使用等待单条线程执行完毕之后,再让下一个线程执行,这样会导致等待时间较长
        //效率低下,等待一个一个的线程执行结束,如果修改成功,同步收取,一个一个的执行,防止竞争
        //关系的产生,造成最终的处理结果丢失
    }
    WaitForMultipleObjects(100, thArr, TRUE, INFINITE);
    time(&end);
    printf("difftime = %lf \n", difftime(end, start));
    printf("num = %d \n", num);

    DeleteCriticalSection(&cs);
    system("pause");
}



//01.多线程问题:冻结&解冻之后
//  1.通过队列,通过一些额外的方式对多线程进行一些额外的操作
//  2.多线程冲突问题:开发中间经常遇到的问题
//      多条线程同时访问一个全局变量,容易出现多线程并发访问问题
//02.全局变量:
//  任何一条线程都可以进行该变量的访问,所以多线程在访问的时候,非常容易出问题,所以在进行大数据处理
//  的时候,将所有的数据全部加载进内存之后,如果采用多线程并发访问,就需要首要解决这个问题!
//03.解决多线程并发访问问题:
//  1.同步单条线程:缺点就是单条线程的操作时间过长!->效率低下,耗费时间
//  2.因此需要使用临界区进行各条不同的线程之间的同一时刻的屏蔽操作    
//04.计时函数编程:
//  1.#include <time.h>
//  2.定义->起始(时刻)->终止(时刻)->差距->显示
//      time_t start,end;
//      time(&start);//记录起始
//      time(&end);//记录结束
//      difftime(end,start);//统计差距
//      %lf//显示结果
//05.定义临界区:
//  1.修改后缀为.cpp
//  2.定义步骤:
//      声明临界区:
//          CRITICAL_SECTION cs;//实质就是一个"结构体"
//          解释:LONG LockCount;LONG RecursionCount;[Lock信号+方式]
//          特点:该临界区既可以应用于C++同样也可以应用于C当中
//          原则:一般情况之下,我们应用多线程都是采用CPP
//      进入临界区:
//          EnterCriticalSection(&cs);
//      离开临界区:
//          LeaveCriticalSection(&cs);
//      删除临界区:
//          特点:临界区不是一个变量,实质上是操作系统,让我们的这个线程进入到临界区锁定状态
//              临界区位于Windows.h头文件当中(多线程最高级是由操作系统完成调度的)
//          DeleteCriticalSection(&cs);
//06.临界区原理:保证精确操作
//  1.并行操作情况之下,效率提升,但是结果不精确-->推出临界区概念:
//      临界区用于解决同步单条线程的效率低下问题,线程进入临界区之后,该条线程独享代码执行权
//  2.临界区的概念:媒婆特点
//07.CreateThread();与_beginthread();的区别
//      CreateThread();使用的是HANDLE
//      _beginthead();使用的是int
//          属于process.h线程库当中的一个线程函数声明,这里只能给你发生互锁的情况,但是不能解决多线程的冲突问题,所以这儿需要使用
//          CreateThead();线程访问的时候发生的冲突情况[只能加锁,弹出多线程访问异常]
//08.代码调试的方式:
//      逐步放开部分代码进行观察
///02.CreateThread.c
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <time.h>

//01.定义"临界区"为全局变量:
//  原因:让多条线程识别同一个临界区
CRITICAL_SECTION cs;

//02.定义"多线程"的通信信息:
//  多线程信息容易导致多线程并发访问冲突问题
int num = 0;

//03._beginthread();线程任务函数格式
//void myFun(void * p)
//{
//  for (int i = 0; i < 100; ++i)
//  {
//      EnterCriticalSection(&cs);
//      ++num;
//      LeaveCriticalSection(&cs);
//  }
//}

//04.CreateThread();线程任务函数格式
//  DWORD:unsigned long
//  WINAPI:声明让操作系统来调度这条线程[操作系统调度标识]
DWORD WINAPI myFun(void * p)
{
    EnterCriticalSection(&cs);
    for (int i = 0; i < 100; ++i)
    {
        ++num;
    }
    LeaveCriticalSection(&cs);
    return 0;
}

int main02(void)
{
    InitializeCriticalSection(&cs);
    time_t start, end;
    time(&start);
    HANDLE thArr[100] = { 0 };
    for (int i = 0; i < 100; ++i)
    {
        thArr[i] = CreateThread(NULL, 0, myFun, NULL, 0, NULL);
        //WaitForSingleObject(thArr[i], INFINITE);
    }
    WaitForMultipleObjects(100, thArr, TRUE, INFINITE);
    time(&end);
    printf("difftime = %lf \n", difftime(end, start));
    printf("num = %d \n", num);
    DeleteCriticalSection(&cs);

    system("pause");
}



//01.临界区:
//  CRITICAL_SECTION:处于"内核部位",它的实现是依赖于操作系统的"内核"
//  所以这里创建线程的方式使用CreateThread();[系统特点&HANDLE]
//02.由于CreateThread();函数来源于Windows内部,所以该函数的参数会很多,于是进行函数改良
//  声明:DWORD WINAPI
//  创建:CreateThread(NULL,0,myfun,NULL,0,NULL);
//      NULL:附加信息,安全等级
//      0:堆栈大小:
//      myfun:函数指针
//      NULL:参数列表
//      0:优先等级
//      NULL:线程编号
//03.这儿的临界区还是存在一定的问题

程序片段(03):_beginthread.c
内容概要:线程池

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <process.h>

//01.创建线程的两种方式:
//  实质:CreateThread();
//  表象:_beginthread();
//02.结束线程的三种方式:
//  内部:
//      endthread();
//      exitthread();
//  外部:
//      terminate();
//03.线程状态的两种控制方式:
//  冻结:SuspendThread(hd);
//  解冻:ResumeThread(hd);

//04.CreateThread();创建线程所需线程函数格式:
//  DWORD:unsigned long
DWORD WINAPI myFun(void * p)
{
    int i = 0;
    while (++i)
    {
        printf("%d \n", i);
        if (i > 8000)
            _endthread();
        Sleep(1000);
    }
    return 0;
}

//05.主线程与辅助线程:
//  1.主线程:处理主函数的线程
//      在整个进程当中起到主导作用,管控整个进程内部的其它所有线程
//  2.辅助线程:处理其它函数的线程
//注:调试模式下,如果"断点"主线程,那么其它线程将会暂停运行状态
//  暂停主线程就相当于占用了品目刷新线程,于是屏幕将不会发生变化         
int main01(void)
{
    HANDLE th = CreateThread(NULL, 0, myFun, NULL, 0, NULL);

    system("pause");
    SuspendThread(th);//冻结
    system("pause");
    ResumeThread(th);//解冻
    system("pause");
    //ExitThread(0);//错误位置
    TerminateThread(th, 0);//外部强行退出
    //_endthread();
    //system("pause");

    system("pause");
}



//01.操作线程:线程调度问题
//      开启-->冻结-->解冻-->内部结束-->外部结束
//02.重点内容:冻结&解冻
//      操作单条线程
//03.CreateThead();线程
//  1.DWORD WINAPI&return 0;
//  2.HANDLE hd=CreateThead(NULL,0,fun,NULL,0,NULL);
//      NULL:安全等级
//             0:堆栈大小,默认为0就是拷贝自身,每个线程的栈内存都是独立的,而非共享的
//         fun:函数指针
//     NULL:参数列表
//            0:优先等级
//    NULL:线程标识
//04.管理调度线程池:
//05.演示或者操作线程的时候,一般情况之下不要使用IDE进行演示:
//  1.注意线程的优先等级
//  2.关闭线程的特点[主线程与副线程的区别]
//  3.进程依然存在:
//      如果是程序自己结束就会自动结束所有线程并结束该进程,但是这个工具是应用
//      底层操作系统内核驱动来完成的,所以它的操作方法比较高端,于是手动结束的时候
//      线程挂掉,但是进程却没有退出
//06.结束线程的三种方式:
//  内部结束:_endthread();+exitthread();
//  外部结束:terminatethread();
//  全部结束:CloseThreadPool();//关闭线程池
//07.冻结线程与解冻线程:
//  SuspendThread();+ResumeThead();
//08.强制结束一条线程:数据容易丢失
//  自我结束语强行结束:return
//  非强制:内部
//      _endThread();//指定线程
//      ExitThread(0);//默认参数
//  强制:外部
//      TerminateThead(hd,0):
//          0代表退出的时候携带一个编号
//09.线程内部的等待采用死循环进行实现
//10.外部操作线程完毕

程序片段(04):01._beginthread.c+02.CreateThead.c
内容概要:线程池

///01._beginthread.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <Windows.h>

//01.提取创建线程时期由创建函数传递给线程函数的参数:
//  1.读取创建线程传递的参数列表,需要进行类型转换
//  2.空类型的地址(干地址)-->强制类型转换-->获取数据
void run(void * p)
{
    char str[10] = { 0 };
    sprintf(str, "备胎%d", *(int *)p);
    printf("%s \n", str);
}

//02.防止多线程并发访问冲突的方式:
//  1._beginthead(run,0,&a[i]);解释
//      (1).多个线程不可以同时访问同一个地址,防止多线程访问冲突
//      (2).参数列表说明:
//          run:函数指针
//              0:堆栈大小
//        &a[i]:参数列表-->这儿的每条线程所访问到的数据都不一样
//          独立访问而非并发访问
//  2._beingthead();函数的返回值是int类型
//      因此_beginthread();和CreateThread();所采取的控制多线程
//      异步操作的方式不一样(一个是int类型的数组,一个是HANDLE类型的数组)
int main01(void)
{
    int arr[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    for (int i = 0; i < 10; ++i)
    {
        int id = _beginthread(run, 0, &arr[i]);
        WaitForSingleObject(id, 300);
    }

    system("pause");
}
///02.CreateThead.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

DWORD WINAPI myFun01(void * p)
{
    int i = 0;
    while (++i)
    {
        printf("%d \n", i);
        Sleep(1000);
    }
    return 0;
}

int main02(void)
{
    HANDLE th = CreateThread(NULL, 0, myFun01, NULL, 0, NULL);

    system("pause");

    system("pause");

    system("pause");
}

DWORD WINAPI myFun02(void * p)
{
    char str[10] = { 0 };
    sprintf(str, "备胎%d", *(int *)p);
    MessageBoxA(0, str, str, 0);
    return 0;
}

//01.CreateThread();方式创建线程详解:
//  1.控制多条线程异步执行状态的所需数组:
//      HANDLE类型的句柄数组
//  2.创建线程所需的参数说明:
//      NULL:安全属性集
//             0:堆栈尺寸,默认拷贝主线程的栈大小,栈独立
//      myfun:函数指针
//          a+i:参数地址
//              0:优先等级
//      NULL:线程编号
int main02(void)
{
    int arr[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    HANDLE thArr[10] = { 0 };
    for (int i = 0; i < 10; ++i)
    {
        thArr[i] = CreateThread(NULL, 0, myFun02, thArr + i, 0, NULL);
        //WaitForSingleObject(thArr[i], INFINITE);
    }
    WaitForMultipleObjects(10, thArr, FALSE, INFINITE);

    system("pause");
}

程序片段(05):多线程.c
内容概要:操作一条线程

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <process.h>

//01.创建一条线程的两种方式:
//  底层方式:CreateThread();
//  表象方式:_beginthread();
//02.结束一条线程的三种方式:
//  内部方式:_endthread();+ExitThread();
//  外部方式:TerminateThread();
//03.线程状态控制的两种方式:
//  冻结:SuspendThread();
//  解冻:ResumeThread();
DWORD WINAPI myFun(void * p)
{
    int i = 0;
    printf("%d \n", i);
    while (++i)
    {
        printf("%d \n", i);
        if (i > 8000)
        {
            //_endthread();
            //ExitThread(0);
        }
    }
    return 0;
}

int main01(void)
{
    HANDLE th = CreateThread(NULL, 0, myFun, NULL, 0, NULL);

    system("pause");
    SuspendThread(th);
    system("pause");
    ResumeThread(th);
    system("pause");
    TerminateThread(th, 0);

    system("pause");
}

程序片段(06):临界区.c
内容概要:线程临界区解决线程冲突

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

//01.临界区的使用与操作系统调度线程相关:
//  所有线程的本质调度还是得依赖于操作系统本身
//  #include <Windows.h>--->临界区结构体
#define N 20

int num = 0;

//02.临界区结构体变量:
//  1.一个用于收钱,一个用于用钱
//  2.定义为全局变量的原因是:
//      为了让多条线程识别同一临界区
CRITICAL_SECTION cs1;
CRITICAL_SECTION cs2;

//03.对临界区的优化操作:
//  减少频繁的进出临界区的次数;
//  有利于提高多线程并发访问的效率
DWORD WINAPI add(void * p)
{
    EnterCriticalSection(&cs1);//减少进出
    for (int i = 0; i < 10000; ++i)
    {
        //EnterCriticalSection(&cs1);//频繁进出
        ++num;
        //LeaveCriticalSection(&cs1);//效率低下
    }
    LeaveCriticalSection(&cs1);//效率提升
    return 0;
}

//04.使用临界区的注意事项:
//  1.临界区结构体变量必须是全局变量
//  2.临界区结构体变量最好不要嵌套使用
DWORD WINAPI sub(void * p)
{
    EnterCriticalSection(&cs2);
    for (int i = 0; i < 10000; ++i)
    {
        --num;
    }
    LeaveCriticalSection(&cs2);
    return 0;
}

int main(void)
{
    //01.初始化临界区结构体变量
    InitializeCriticalSection(&cs1);
    InitializeCriticalSection(&cs2);
    //02.匿名代码块儿的使用方式:
    //  划分区域执行代码(复用代码)
    //03.要想多线程异步执行状态:
    //  需要使用多线程句柄数组
    {
        HANDLE thArr[N] = { 0 };
        for (int i = 0; i < N; ++i)
        {
            thArr[i] = CreateThread(NULL, 0, add, NULL, 0, NULL);
            //WaitForSingleObject(thArr[i], INFINITE);
        }
        WaitForMultipleObjects(N, thArr, TRUE, INFINITE);
        printf("num = %d \n", num);
    }
    {
        HANDLE thArr[N] = { 0 };
        for (int i = 0; i < N; ++i)
        {
            thArr[i] = CreateThread(NULL, 0, sub, NULL, 0, NULL);
        }
        WaitForMultipleObjects(N, thArr, TRUE, INFINITE);
        printf("num = %d \n", num);
    }
    //04.释放临界区结构体变量
    DeleteCriticalSection(&cs1);
    DeleteCriticalSection(&cs2);
    system("pause");
}



//01.什么是临界区?
//  1.临界区是用于解决多线程并发访问冲突问题的
//  2.包括Cocos2dx当中的C++封装线程,基本上都是
//      我们现在所使用的同步方式(这里的线程操作方式就是实质)
//  3.临界区所支持的最大线程个数为64,Windows操作系统之上
//      线程个数限制,但是服务器操作系统上面没有限制
//  4.临界区的使用步骤:
//      创建-->初始化-->进入-->离开-->删除
//02.线程通信-->进程通信:
//      并发访问错误:收钱特点,收不过来之后
//03.防止多线程并发访问错误:
//  1.WaitForSingleObject(hd[i]);等待单个线程逐个退出-->缺点:效率低下
//  2.避免多线程并发访问问题,并且提高多线程操作效率的方式:
//      临界区-->跑到最近的位置-->逐个给钱[以前的情况是一个一个的处理]
//          临界区&互斥量
//04.临界区的完整使用过程:
//  创建临界区:全局声明
//      CRITICAL_SECTION cs;
//  初始化临界区:Main();
//      InitializeCriticalSection(&cs);
//  使用临界区:
//      进入:EnterCriticalSection();
//      退出:LeaveCriticalSection();
//  释放临界区:DeleteCriticalSection();
//      1.因为临界区结构体变量内部存在指针情况,会耗费内存,所以需要释放临界区
//      2.还有原因就是因为我们这里申请的是操作系统调度(避免过度占用操作操作系统资源)
//05.线程库的初始化特点:
//  C语言不会自动进行初始化,C++会进行自动的初始化动作
//06.解决多线程并发访问冲突的两种方式最大特点:
//  同步方式:
//      1.效率低下
//      2.一次只有一条线程处于执行状态
//  临界方式:
//      1.效率优化
//      2.一次拥有多条线程处于执行状态
//注:临界方式一次有(N-1)条线程处于等待执行状态,但是总有一条是处于执行状态
//注:同步方式和临界方式最大的区别就在于处于等待执行状态的线程数目不同
//      同步方式:没有等待执行状态的线程
//      临界方式:用于等待执行状态的线程
//  相当于一个有预热状态,一个没有预热状态!

程序片段(07):01.事件.c+02.事件.c+03.事件.c
内容概要:线程通信事件机制

///01.事件.c
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

//01.事件:确定为3个
HANDLE eventArrA[3] = { 0 };
HANDLE threadArrA[3] = { 0 };
//02.同步:两个线程-->三个线程-->多个线程
DWORD WINAPI firstThread(void * p)
{
    MessageBoxA(0, "1", "1", 0);
    printf("第一个线程执行完毕! \n");
    SetEvent(eventArrA[0]);//设置事件(发出Event通知)
    return 0;
}

//03.事件用于多条线程之间的通信:
//  可以用于控制多线程的情况之下,各条线程之间的执行顺序
//注:设置事件相当于发出通知,等待事件相当于处理通知
DWORD WINAPI secondThread(void * p)
{
    //等待一个事件执行完毕[等待Event信号的出现,再执行下一步操作]
    WaitForSingleObject(eventArrA[0], INFINITE);//处理事件(等待Event通知)
    MessageBoxA(0, "2", "2", 0);
    printf("第二个线程执行完毕! \n");
    return 0;
}

//04.句柄类型:
//  创建事件的方式+创建线程的方式
int main01(void)
{
    //1.初始化事件参数:
    //  NULL:安全等级+TRUE:人为设定+FALSE:触发状态+NULL:事件名称
    eventArrA[0] = CreateEvent(NULL, TRUE, FALSE, NULL);
    eventArrA[1] = CreateEvent(NULL, TRUE, FALSE, NULL);
    //2.两条线程并行弹出盒子,现在需要控制线程的执行顺序:
    threadArrA[0] = CreateThread(NULL, 0, firstThread, NULL, 0, NULL);
    threadArrA[1] = CreateThread(NULL, 0, secondThread, NULL, 0, NULL);
    //3.维持多线程异步执行状态:
    WaitForMultipleObjects(2, threadArrA, TRUE, INFINITE);
    printf("全部等待完成! \n");

    system("pause");
}



//01.线程同步的问题:
//  1.服务器当中的多条线程:
//      (1).将外部数据写入到本地文件
//      (2).从本地文件读取数据到内存
//  2.相关问题:
//      (1).没有写入之前无法进行读取
//      (2).但是这两条线程是同时开启的
//  3.模型:
//      (1).文本-->写入-->读取[配置文件特点]
//      (2).先写入本地文件,再进行本地文件读取
//      (3).同时开启的时候,就需要进行多线程通信,以确保线程的执行流程
//02.线程通信:事件机制[用于管理线程通信]
//  1.同时启动的两条线程,将会任意确定执行顺序,所以需要线程调度
//  2.实现时间等待的特点(线程通信)
//03.事件用作通知:CreateEvent();事件处理机制
//  A:默认的安全设定0
//  B:TRUE人工设定&FALSE自动设定
//  C:是否进入到触发状态
//  D:事件名称
//04.执行流程:聊天儿系统[多事件处理]
//  1.等待执行原理的使用
//  2.创建事件,线程都是在主函数内完成
//      (1).事件必须是全局变量(多条线程访问统一标识!)
//      (2).线程必须是全局变量(任意函数都可以创建线程)
//      (3)SetEvent(e);WaitForSingleObject(e);
//          设置(触发)+等待(处理)<->发出通知+处理通知
//05.聊天儿系统原理:
//  1.多个时间连续
//  2.多层嵌套问题
///02.事件.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

HANDLE threadArrB[3] = { 0 };//多线程处理
HANDLE eventArrB[4] = { 0 };//线程通信

//01.全局变量:所有线程使用同一临界区标识
//  用于解决全局的多线程并发访问冲突问题
CRITICAL_SECTION csB;//线程互斥

//02.全局变量:多线程之间的通信内容
//  1.代表聊条儿内容的缓冲区
//  2.memset(str, ‘\0‘, 1024);
//      暂时用不到,三方通话机制需要使用
//  3.三方通信原理:中介者设计模式
//      海华和芳芳不会直接接触的特点;
//      都是通过媒婆进行间接接触的特点
char strB[1024] = { 0 };//通信内容

//03.三方通信原理简介:
//  海华-->媒婆(设置事件0)
//      媒婆(处理事件0)
//  媒婆-->芳芳(设置事件1)
//      芳芳(处理事件0)
//  芳芳-->媒婆(设置事件2)
//      媒婆(处理事件2)
//  媒婆-->海华(设置事件3)
//      海华(处理事件3)
DWORD WINAPI haiHuaB(void * p)
{
    int i = 1;
    EnterCriticalSection(&csB);
    memset(strB, ‘\0‘, 1024);//防止并发访问冲突
    sprintf(strB, "haiHua第%d次说:i love you fangfang! \n", i);//三方通信
    LeaveCriticalSection(&csB);
    Sleep(1000);//不会占用过多的互斥时间(模拟数据生成时间)
    SetEvent(eventArrB[0]);//发出通知
    //00:模拟第一次的数据设置(通信主动发出者:主动者)
    while (++i)
    {
        WaitForSingleObject(eventArrB[3], INFINITE);//等待通知(必须等待)
        EnterCriticalSection(&csB);
        memset(strB, ‘\0‘, 1024);//内容清空
        sprintf(strB, "haiHua第%d次说:i love you fangfang! \n", i);
        LeaveCriticalSection(&csB);
        Sleep(1000);//模拟现实
        SetEvent(eventArrB[0]);
    }
    return 0;
}

DWORD WINAPI ruiFuB(void * p)
{
    int i = 0;
    while (++i)
    {
        WaitForSingleObject(eventArrB[0], INFINITE);
        EnterCriticalSection(&csB);
        printf("媒婆给HaiHua传递:%s \n", strB);
        LeaveCriticalSection(&csB);
        Sleep(1000);
        SetEvent(eventArrB[1]);
        WaitForSingleObject(eventArrB[2], INFINITE);
        EnterCriticalSection(&csB);
        printf("媒婆传递FangFang:%s \n", strB);
        LeaveCriticalSection(&csB);
        Sleep(1000);
        SetEvent(eventArrB[3]);
    }
    //WaitForSingleObject(eventArrB[0], INFINITE);
    //printf("%s \n", strB);
    //SetEvent(eventArrB[1]);
    //WaitForSingleObject(eventArrB[2], INFINITE);
    //printf("%s \n", strB);
    return 0;
}

DWORD WINAPI fangFangB(void * p)
{
    int i = 0;
    while (++i)
    {
        WaitForSingleObject(eventArrB[1], INFINITE);
        EnterCriticalSection(&csB);
        memset(strB, ‘\0‘, 1024);
        sprintf(strB, "fangFang第%d次说:i love you haihua! \n", i);
        LeaveCriticalSection(&csB);
        Sleep(1000);
        SetEvent(eventArrB[2]);
    }
    return 0;
}

int main02(void)
{
    InitializeCriticalSection(&csB);

    eventArrB[0] = CreateEvent(NULL, TRUE, FALSE, NULL);
    eventArrB[1] = CreateEvent(NULL, TRUE, FALSE, NULL);

    threadArrB[0] = CreateThread(NULL, 0, haiHuaB, NULL, 0, NULL);
    threadArrB[1] = CreateThread(NULL, 0, ruiFuB, NULL, 0, NULL);
    threadArrB[2] = CreateThread(NULL, 0, fangFangB, NULL, 0, NULL);

    WaitForMultipleObjects(3, threadArrB, TRUE, INFINITE);
    printf("over!!! \n");

    DeleteCriticalSection(&csB);
    system("pause");
}



//01.双方通信的原理:你发一条,我收一条,然后我再发送一条

以上是关于20160226.CCPP体系详解(0036天)的主要内容,如果未能解决你的问题,请参考以下文章

20160227.CCPP体系详解(0037天)

20160225.CCPP体系详解(0035天)

20160206.CCPP体系详解(0016天)

20160219.CCPP体系详解(0029天)

20160218.CCPP体系详解(0028天)

20160210.CCPP体系详解(0020天)