20160227.CCPP体系详解(0037天)
2016-03-27 16:44
309 查看
程序片段(01):01.一对一模式.c+02.中介者模式.c+03.广播模式.c
内容概要:事件
程序片段(02):01.Semaphore.c+02.SemaphoreNew.c
内容概要:信号量
程序片段(03):01.互斥.c+02.互斥读写.c
内容概要:互斥锁
程序片段(04):Mutex.c
内容概要:01.跨进程Mutex(发互斥)
程序片段(05):Mutex.c
内容概要:02.跨进程Mutex(收互斥)
程序片段(06):发事件.c
内容概要:01.跨进程Event(发事件)
程序片段(07):收事件.c
内容概要:02.跨进程Event(收事件)
程序片段(08):发信号.c
内容概要:01.跨进程Semaphore(发信号)
程序片段(09):收信号.c
内容概要:02.跨进程Semaphore(收信号)
程序片段(10):TimePrc.c
内容概要:时间同步
程序片段(11):
内容概要:Volatile
程序片段(12):Queue.h+Queue.c+数组顺序队列.c
内容概要:01.数组顺序队列
程序片段(13):CircleQueue.h+CircleQueue.c+数组正向环形队列.c
内容概要:02.数组环形顺序队列
程序片段(15):CircleQueue.h+CircleQueue.h+数组正向环形队列.c
内容概要:01.数组正向环形队列
程序片段(16):Queue.h+Queue.c+数组正向环形队列.c
内容概要:02.数组正向环形队列(标准版)
程序片段(17):CircleQueue.h+CircleQueue.c+数组正向环形队列.c
内容概要:01.数组正向环形队列
程序片段(18):Queue.h+Queue.c+Main.c
内容概要:02.链表反向队列
程序片段(19):Queue.h+Queue.c+01.Event通信(生产者消费者).cpp+02.Semaphore通信(生产者消费者).c
内容概要:03.生产者与消费者模式
内容概要:事件
///01.一对一模式.c #include <stdio.h> #include <stdlib.h> #include <Windows.h> //01.关于多线程: // (临界区+互斥量):线程冲突 // (事件):线程通信 // (时间):同步线程 HANDLE eventArrA[2] = { 0 }; HANDLE threadArrA[2] = { 0 }; DWORD WINAPI haiHua(void * p) { printf("海华第01次说:i love you fangFang, please help me debug! \n");//信息通信内容 Sleep(1000);//信息传递时间 SetEvent(eventArrA[0]);//提示信息传到 int i = 1; while (++i) { WaitForSingleObject(eventArrA[1], INFINITE);//等待信息传到 printf("海华第%02d次说:i love you fangFang, please help me debug! \n", i); Sleep(1000); //ResetEvent(eventArrA[1]);//重置信息提示(手动) SetEvent(eventArrA[0]); } return 0; } DWORD WINAPI fangFang(void * p) { int i = 0; while (++i) { WaitForSingleObject(eventArrA[0], INFINITE); printf("王芳第%02d次说:sorry! but i love you! \n", i); Sleep(1000); SetEvent(eventArrA[1]); } return 0; } //02.关于CreateEvent(arg1, arg2, arg3, arg4); // arg1:安全属性集---->通常用NULL // arg2:手动重置事件-->手动:TRUE|自动:FALSE // 注:使用一次事件通知,用TRUE,使用多次事件通知,用FALSE // 注:使用一次线程通信,通常用的是信号量机制,而不是事件机制 // arg3:事件激活状态-->通常用FALSE // arg4:事件唯一名称-->自定义(便于检索指定事件) int main01(void) { eventArrA[0] = CreateEvent(NULL, FALSE, FALSE, NULL); eventArrA[1] = CreateEvent(NULL, FALSE, FALSE, NULL); threadArrA[0] = CreateThread(NULL, 0, haiHua, NULL, 0, NULL); threadArrA[1] = CreateThread(NULL, 0, fangFang, NULL, 0, NULL); WaitForMultipleObjects(2, threadArrA, TRUE, INFINITE);//维持多线程异步并发执行的状态 system(""); } //01.事件深入: // 1.事件用于线程通信 // 2.事件的额外细节:三个案例 // 双方通话---->三方通话---->一对多模式 // (相亲) (媒婆)中介者 (广播)广播模式 //02.了解一些问题: // (临界区+互斥+原子变量):线程冲突 // 事件:线程通信 // 时间:同步线程 //03.什么是死锁? // 编写事件的时候最容易遇到死锁的事情! //04.现在我们需要几个信号量,而且这个信号量我们用什么来进行描述? // 时间通知+信号量 //05.顺序不严密: // 1.等待信号之后,信号需要复原才行,否则会出现不正常的情况(信号错乱!) // 2.信号不同步和乱序的解决方案-收到信号之后进行复位 // (1).信号复位情况--必须复位,某些情况之下自动复位,建议主动复位 // (2).围绕信号:的False&TRUE的分别 // 第二个参数:密切相关-自动&手动[复位情况]
///02.中介者模式.c #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <Windows.h> HANDLE threadArrB[3] = { 0 }; HANDLE eventArrB[4] = { 0 }; char strB[256] = { 0 };//线程通信内容 //01.三方通话:中介者设计模式 // 海华向中介者:发出事件通知0 // 中介者等海华:等待事件通知0 // 中介者向芳芳:发出事件通知1 // 芳芳等中介者:等待事件通知1 // 芳芳向中介者:发出事件通知2 // 中介者等芳芳:等待事件通知2 // 中介者向海华:发出事件通知3 // 海华等中介者:等待事件通知3 // 海华向中介者:发出事件通知0 DWORD WINAPI haiHuaB(void * p) { sprintf(strB, "海华第01次说:i love you fangFang, please help me debug! \n");//发出通信内容 Sleep(1000);//模拟通信时间 SetEvent(eventArrB[0]);//提示通信到达 int i = 1; while (++i) { WaitForSingleObject(eventArrB[3], INFINITE); memset(strB, '\0', 256); sprintf(strB, "海华第%02d次说:i love you fangFang, please help me debug! \n", i); Sleep(1000); SetEvent(eventArrB[0]); } return 0; } DWORD WINAPI ruiFuB(void * p) { int i = 0; int status = 0;//切换通信对象 while (++i) { if (!status) { WaitForSingleObject(eventArrB[0], INFINITE); printf("媒婆传递海华通信内容(传递次数:%02d): \n", i); printf("\t%s \n", strB); Sleep(1000); SetEvent(eventArrB[1]); status = 1; } else { WaitForSingleObject(eventArrB[2], INFINITE); printf("媒婆传递芳芳通信内容(传递次数:%02d): \n", i); printf("\t%s \n", strB); Sleep(1000); SetEvent(eventArrB[3]); status = 0; } } return 0; } DWORD WINAPI fangFangB(void * p) { int i = 0; while (++i) { WaitForSingleObject(eventArrB[1], INFINITE); memset(strB, '\0', 256); sprintf(strB, "王芳第%02d次说:sorry! but i love you! \n", i); Sleep(1000); SetEvent(eventArrB[2]); } return 0; } int main02(void) { eventArrB[0] = CreateEvent(NULL, FALSE, FALSE, L"haiHua"); eventArrB[1] = CreateEvent(NULL, FALSE, FALSE, L"ruiFuToFang"); eventArrB[2] = CreateEvent(NULL, FALSE, FALSE, L"fangFang"); eventArrB[3] = CreateEvent(NULL, FALSE, FALSE, L"ruiFuToHua"); 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); system("pause"); } //01.中介者模式&广播模式&图论模式[多对多] // 中介者:三方 // 广播:一对多 // 图论:多对多 //注:多线程这块儿必须要会树和图 //02.一对多的情况下: // 既可以采用数组进行管理也可以采用链表进行管理 //03.涉及到树的情况之下: // QQ群聊天儿多线程,练就数据结构 //04.一对多&多对多: // 群聊原理:中介者-->每个人进行转发 // 中介者进行转发原理-->数组管理-->数组广播-->群聊模式 //05.流程梳理: // 1.我发送一条消息,中介者收到之后,他进行群发动作 // 2.中介者的衍生模式-->形成闭环-->选好起始点 //06.编程思想:精髓 // 原则上一个消息全局变量读取特点 // 相当于是一个公告栏,权限读取 //07.操作: // 定义全局变量,便于读取全局变量的数据
///03.广播模式.c #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <Windows.h> HANDLE threadArrC[10] = { 0 }; HANDLE eventArrC[1] = { 0 }; char strC[256] = { 0 };//线程通信内容 char chrArr[10] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K' };//代表十个人 //01.一个线程发出事件通知消息,多条线程监听该事件通知消息 // 一对多的模式 DWORD WINAPI haiHuaC(void * p) { char * pChr = (char *)p; printf("i am %c haiHua's gir friend! \n", *pChr); if ('A' == *pChr) { MessageBox(0, TEXT("1"), TEXT("1"), 0);//暂停通知线程 sprintf(strC, "海华女友%c speak:xiaohuahua lovely! \n", *pChr);//消息通知内容 SetEvent(eventArrC[0]);//发出事件通知 MessageBox(0, TEXT("1"), TEXT("1"), 0);//暂停通知线程 sprintf(strC, "海华女友%c speak:xiaohuahua lovely! \n", *pChr);//消息通知内容 SetEvent(eventArrC[0]);//发出事件通知 } int i = 0; while (++i) { WaitForSingleObject(eventArrC[0], INFINITE);//等待事件通知 printf("haiHua's girl friend %c read %s! \n", pChr, strC); Sleep(1000); ResetEvent(eventArrC[0]); } return 0; } int main03(void) { eventArrC[0] = CreateEvent(NULL, TRUE, FALSE, NULL); for (int i = 0; i < 10; ++i) { threadArrC[i] = CreateThread(NULL, 0, haiHuaC, &chrArr[i], 0, NULL); } WaitForMultipleObjects(10, threadArrC, TRUE, INFINITE); system("pause"); } //01.中介者设计模式之广播模式:QQ群聊原理 // 群聊-->数组-->链表-->环状-->局域网:环状结构[网络可靠性] //02.QQ的开发:不仅有发送和收取消息-->所以线程非常多 // 信号对称-->需要进行手动进行事件的设置 // 一对一:自动 // 中介者:自动 // 一对多:手动 // 多对多:手动
程序片段(02):01.Semaphore.c+02.SemaphoreNew.c
内容概要:信号量
///01.Semaphore.c #include <stdio.h> #include <stdlib.h> #include <Windows.h> //01.信号量: // 1.类似于空位的特点: // 2.空置多少个位置就可以容纳多少个并行线程执行 //注:当空余的位置为0的时候,也就不能在另起线程执行任务了 #define id L"haiHua"//信号ID #define MAX 3//空位数 //02.多线程信号量(semaphore)通信: // 1.特点:打开一个信号量(等同于检索一个指定ID名称的信号量) // 2.格式:HANDLE mySema = OpenSemaphore(arg1, arg2, arg3); // arg1:信号量检索范围(SEMAPHORE_ALL_ACCESS) // arg2:继承特性 // arg3:信号量检索名称(自定义名称,在固定范围内唯一标识信号量) // 3.刚打开信号量的时候: // 信号量的空位为0,也就是无法开启新的异步线程执行任务 // 信号量的空位为N,也就是说此刻可以开启N条异步线程执行任务代码 //注:空位为N,表示除开当前线程之外可以另起的异步线程个数 DWORD WINAPI myWorker(void * p) { int * pInt = (int *)p; printf("worker:%d si running! \n", *pInt); HANDLE mySema = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, id);//指定范围检索指定名称的信号量 if (mySema)//判断信号量是否存在 { printf("worker:%d is waiting! \n", *pInt);//表示当前线程处于判定信号量状态 Sleep(1000); if (WaitForSingleObject(mySema, INFINITE) == WAIT_OBJECT_0)//等待空位为0 {//如果信号空位为0,也就是无法开启异步线程的情况 printf("worker:%d is getting! \n", *pInt);//此时只有当前线程获得CPU执行权,其它线程无法获取CPU可执行权 Sleep(3000); printf("worker:%d is leaving! \n", *pInt); ReleaseSemaphore(mySema, 1, NULL);//释放信号:只是打开一个空位,也就是可以开启另外一条异步线程进行执行了 CloseHandle(mySema);//释放资源信号量资源 } } return 1; } //03.创建信号量: // 1.特点:初始化信号量对象 // 2.格式:HANDLE mySema = CreateSemaphore(arg1, arg2, arg3, arg4); // arg1:安全属性集 // arg2:初始空位数 // arg3:最大空位数 // arg4:信号量名称 int main01(void) { HANDLE mySema = CreateSemaphore(NULL, 0, MAX, id); HANDLE threadArr[10] = { 0 }; for (int i = 0; i < 10; ++i) {//由于当前信号量为0,因此多条执行同一段儿代码的时候,需要判定能否通过 threadArr[i] = CreateThread(NULL, 0, myWorker, threadArr + i, 0, NULL); } Sleep(5000); printf("激活状态! \n"); ReleaseSemaphore(mySema, MAX, NULL);//释放信号量 WaitForMultipleObjects(10, threadArr, TRUE, INFINITE); CloseHandle(mySema); system("pause"); } //01.信号量:Semaphore // 1.量值:0-1-2-3 // 2.使用一次进行一次减数,到了一定的数据之后,做一些指定操作 // 当数据减到至0的时候,信号为0,在使用信号量的地方,处于停滞状态 // 3.信号量还可以做一些其他的限定操作 // 4.线程通信:用途 // 5.具备等待机制 //02.信号计数原理: // 1.两个按键入口,多个行李 // 2.信号衰减原理:空位原理 // 信号为0的时候,没有空位为0,通过判断信号的空位情况,决定是否让线程干活儿 // 等待唤醒机制0与非0的区别(非0,线程可执行,0线程不可执行) //03.关卡原理: // 1.同时最多支持10000个人购票 // 2.如果超过10000个人,就需要排队 // 3.当10000个人购票完毕的时候,重新开启线程执行任务 //04.原理:if 0 等下去 // 1.同一个信号量 // 2.10个线程 // 3.状态判定: // 0-->10个等待 // 5-->5个等待,5个执行 // 执行一次,减去一次-->信号量衰减 // 4.所有的线程都能够读取到该信号量 // 多个线程占用资源:等待执行状态 // 用完与没有用完(线程不可执行状态与线程可执行状态) //05.信号量完全解析: // 步骤一: // HANDLE hSEM=CreateSemaphore(NULL,0,MAX,id); // 创建一个信号量,信号量的最大值为MAX,如果等于0的情况之下,它就在这儿死等下去 // 步骤二: // ReleaseSemaphore(mySema,MAX,NULL);//释放信号量,补充空位数量 // 一旦将信号量设定为5就会开始进行执行 //06.什么样儿的情况之下我们使用信号量? // 实现两个线程的死锁状态,设定为1这个信号量,进或者不进
///02.SemaphoreNew.c #include <stdio.h> #include <stdlib.h> #include <Windows.h> int num = 0; //01.信号量的应用: // 1.排队执行机制 // 让多个线程处于执行状态,让多个线程处于休眠状态 // 2.实现线程互斥 // 让一个线程处于执行状态,让其它所有线程处于等待状态 DWORD WINAPI add(void * p) { HANDLE mySema = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, L"xiaobin");//打开信号量: if (mySema) { if (WAIT_OBJECT_0 == WaitForSingleObject(mySema, INFINITE)) {//判断信号强弱(判断信号量的数目,也就是判断空位数目) for (int i = 0; i < 10000; ++i) { ++num; } ReleaseSemaphore(mySema, 1, NULL); CloseHandle(mySema); } } else { printf("打开信号量失败! \n"); } } int main02(void) { //01.实现线程互斥的关键代码: // 最多只能有一个空位(最多只能同时有一条线程处于执行状态) HANDLE mySema = CreateSemaphore(NULL, 0, 1, L"xiaobin"); HANDLE threadArr[64] = { 0 }; for (int i = 0; i < 64; ++i) { threadArr[i] = CreateThread(NULL, 0, add, NULL, 0, NULL); } printf("激活线程"); ReleaseSemaphore(mySema, 1, NULL); WaitForMultipleObjects(64, threadArr, TRUE, INFINITE); printf("num = %d \n", num); CloseHandle(mySema); system("pause"); } //01.信号量可以实现多个线程的卡顿现象 //02.信号量的空位原理-->0和1的原理:互斥特点 // 入职与离职原理的特点-->空位原理 //03.如何使用信号量实现一个全局变量的自增? // 互斥类型的方式实现-->使用信号量实现线程之间的互斥现象 //04.随机数的求取方法: // 1.原始函数 // 2.多线程的数据丢失 //05.多线程处理状态下的CPU是不会稳定的情况 //06.信号量:0代表没有空位-->线程等待状况 // 控制访问次数 //07.互斥量与信号量的区别: // 互斥量:只能让一个线程处于运行状态 // 信号量:可以让多个线程处于运行状态,其他线程休眠 // 临界区:只能让一个线程处于运行状态 // 时间同步: // 事件:也可以实现让多个线程处于运行状态,其他线程休眠状态 // 原子操作:操作速度最快,因为它不需要等待操作线程,几乎直接运行状态 //注:原子量的速度快在于无需让多条线程处于等待执行状态,其他线程互斥的方式 // 存在着线程等待执行的状态
程序片段(03):01.互斥.c+02.互斥读写.c
内容概要:互斥锁
///01.互斥.c #include <stdio.h> #include <stdlib.h> #include <Windows.h> int static num = 6400000; //01.全局写入锁 // 当一个线程在进行指定变量的写入操作的时候; // 其它线程若是需要写入这个指定变量数据,那么 // 其他线程只能处于等待执行写入数据的状态 SRWLOCK gw_lock = { 0 }; static DWORD WINAPI write(void * p) {//多线程写入状态下,同一时刻只能由一条线程执行写入状态! AcquireSRWLockExclusive(&gw_lock);//获得独立写入锁(进入锁定状态) for (int i = 0; i < 100000; ++i) { --num; } ReleaseSRWLockExclusive(&gw_lock);//释放独立写入锁(释放锁定状态) return 0; } int main01(void) { InitializeSRWLock(&gw_lock); HANDLE threadArr[64]; for (int i = 0; i < 64; ++i) { threadArr[i] = CreateThread(NULL, 0, write, NULL, 0, NULL); } //num += 10000;//没有生效,说明互斥锁的原则是全局生效,是对所有线程生效! WaitForMultipleObjects(64, threadArr, TRUE, INFINITE); printf("num = %d \n", num); system("pause"); } //01.互斥锁的概念:互斥 // 如同交往一个女友之后就被锁定了 //02.互斥锁问题: // 线程互斥:同一时刻,只能由同一个线程执行数据操作 //03.线程的互斥实现方式: // 临界区-->互斥量-->原子操作-->事件-->信号量-->互斥锁 //04.互斥锁的创建流程: // 全局变量:定义互斥量 // SRWLOCK g_lock; // Main函数:初始化互斥量 // InitializeSRWLock(&g_lock); // 数据锁定:写入和读取的锁定 // 线程函数: // AcquireSRWLockExclusive(&g_lock);//锁定写入 // ReleaseSRWLockExclusive(&g_lock);//锁定释放 //05.互斥锁的使用场景: // 1.改变一个变量的时候需要锁定(防止同时读取,同时写入) // 2.读取一个变量的时候也需要锁定(防止同时写入,同时读取)
///02.互斥读写.c #include <stdio.h> #include <stdlib.h> #include <Windows.h> int num = 6400000;//待读写的数据 SRWLOCK g_lock = { 0 };//写入锁 DWORD WINAPI write(void * p) { printf("开始写入! \n"); AcquireSRWLockExclusive(&g_lock); for (int i = 0; i < 100000; ++i) { --num; //Sleep(10); } ReleaseSRWLockExclusive(&g_lock); printf("结束写入! \n"); return 0; } DWORD WINAPI read(void * p) { printf("读取状态! \n"); AcquireSRWLockShared(&g_lock); int i = 0; while (1) { ++i; Sleep(1000); printf("第%d秒, num = %d \n", i, num);//由于写入状态锁定了,因此这里的读取状态,无法读取到数据 if (20 == i) break; } ReleaseSRWLockShared(&g_lock); printf("读取结束! \n"); return 0; } int main02(void) { InitializeSRWLock(&g_lock); CreateThread(NULL, 0, read, NULL, 0, NULL); HANDLE threadArr[64] = { 0 }; for (int i = 0; i < 64; ++i) { threadArr[i] = CreateThread(NULL, 0, write, NULL, 0, NULL); } WaitForMultipleObjects(64, threadArr, TRUE, INFINITE); printf("num = %d \n", num); system("pause"); } //01.互斥锁的读写状态控制 // 写入的状态下不可读取,读取的状态下不可写入 //02.锁定状态,读取完成之后才进行锁定 //03.一个资源只能锁定一次,不能锁定多次 //04.锁定-->防止冲突问题-->读取和写入的状态 // 防止同时写入和读取数据
程序片段(04):Mutex.c
内容概要:01.跨进程Mutex(发互斥)
#include <stdio.h> #include <stdlib.h> #include <Windows.h> char name[100] = "haihualovefang"; int main(void) { //01.创建互斥量"Mutex": // 位于内核层|Ring0层 HANDLE myMutex = CreateMutexA(NULL, TRUE, name); printf("在内核层|Ring0层创建互斥量(Mutex)成功! \n"); char chr = getch();//实时获取单个字符 //02.释放互斥量: // 相当于发出通知 ReleaseMutex(myMutex); //03.关闭互斥量: CloseHandle(myMutex); system("pause"); } //01.关于跨进程的驱动访问:内核对象 // 无论是Windows还是Linux都是存在互斥量说法 //02.如果是跨进程的话: // 创建跨进程的Mutext需要有名称(便于全局访问) //03.编写网络程序的时候: // 既需要编写客户端也需要编写网络端 // -->编写两个程序的时代 //04.演示的时候需要: // 进行编译好的程序之间的演示 //05.跨进程通信: // 1.Event&Mutex&semaphore都可以实现跨进程的线程通信 // 2.Mutex是最安全的跨进程线程访问(因为能够处理发送通知方的断开情况) // 发出通信信息的进程退出情况能够处理!
程序片段(05):Mutex.c
内容概要:02.跨进程Mutex(收互斥)
#include <stdio.h> #include <stdlib.h> #include <Windows.h> char name[100] = "haihualovefang"; int main(void) { //01.打开互斥量: // 作用:获取互斥量 // 格式:HANDLE mutex = OpenMutexA(arg1, arg2, arg3); // mutex:互斥量+arg1:检索范围+arg2:继承句柄+互斥量名称 HANDLE myMutex = OpenMutexA(MUTEX_ALL_ACCESS, TRUE, name);//获取互斥量 //if (NULL == myMutex)//判定互斥量 //{ // printf("获取互斥量失败! \n"); // system("pause"); // return; //} printf("等待状态! \n"); //02.控制互斥量的等待时间: // 先获得互斥量-->设定等待状态时间(等待指定的时间范围!) DWORD res = WaitForSingleObject(myMutex, 10000);//设置等待状态 switch (res) { case WAIT_OBJECT_0: printf("收到跨进程信号! \n"); break; case WAIT_TIMEOUT: printf("等待跨进程信号超时! \n"); break; case WAIT_ABANDONED: printf("另外一个进程发生终止!结束跨进程信号等待状态! \n"); break; default: break; } CloseHandle(myMutex); system("pause"); } //01.刚才的程序特点: // 都是出于同一个进程内的线程操作(同一进程) //02.C++关于"事件"和"信号量"的封装: // 封装通用的一个机制,Cocos2dx的时候都是一样的情况 // 包含OC也一样,只不过它们将接口内容进行了简化 //03.多线程的强化: // 1.event&mutex&semaphore:(驱动层|Ring0层) // 本质:是处于驱动里面的一个综合信号量 // 2.操作系统起到什么作用? // (1).操作系统类似于一个巨大的进程,里面运行的每个程序类似于线程 // (类比:大进程&进程)<--->(进程&线程) // (2).电脑重启,打开多个.exe都需要重启 // (3).操作系统和应用程序之间的关系就如同进程和线程之间的关系 // (4).高级机制:内核对象(Ring0层对象) //04.操作系统的高级机制:内核对象-->项目使用-->跨进程使用 // 1.操作系统的分层机制: // (1).ring0:就是最底层,这里可以用于编写驱动(出错:蓝屏) // (2).ring3:就是应用层,(出错:进程出错) // 2.线程互斥区分机制: // (1).event&mutex: // 这里创建的指针处于应用层,但是指针所指向的内存处于ring0层 // ring0层当中的对象可以看到所有进程的内存(最高访问权限) // (2).进程之间不可以进行相互读写,必须通过注射方式 // (3).event&mutex都是出于ring0层的内核对象 // 本质:对象的底层特点 // 所以:它们不仅可以用于一个进程内的线程互斥,还可以用于多个进程之间的线程互斥 //05.mutex的互斥问题解析: // 1.跨进程的mutex互斥问题 // 2.C++的线程库都是对C语言多线程的封装 // 大概原理-->C++的类使用 //06.关于跨进程通信的问题: // 最好使用互斥量(mutex)实现跨进程通信 // 原因:其他方式(event&semaphore)不能处理进程断开的情况!
程序片段(06):发事件.c
内容概要:01.跨进程Event(发事件)
#include <stdio.h> #include <stdlib.h> #include <Windows.h> char name[100] = "haihualovefang"; //01.Event实现跨进程通信: // 1.这儿的Event不是线程级别的含义,而是进程级别的含义: // 该Event实质上是位于(内核|Ring0层),因此可以实现跨进程通信 // 2.参数说明:第二个参数表示是否重置手动重置事件状态 // TRUE:手动重置+FALSE:自动重置 int main(void) { HANDLE myEvent = CreateEventA(NULL, FALSE, FALSE, name);//创建事件 printf("跨进程Event创建成功! \n"); char chr = getch(); SetEvent(myEvent);//设置事件 printf("发送跨进程Event事件! \n"); CloseHandle(myEvent); system("pause"); } //01.严格区分跨线程和跨进程 //02.使用Event实现跨进程线程访问 //03.Event和Mutex有一定的区别: // Event跨进程不能使用匿名的,否则的话找不到 //注:跨进程一定要采用唯一名称标识信号 //04.TCP/UDP的时候就是如此复杂的情况 //05.一般进程与进程之间都需要设定一个超时等待时间 //06.Event天生的缺陷: // 只有Mutex可以感知到另外一个进程的丢失 // Event不具备感知进程丢失的功能 //注:进程通信情况之下的进程丢失情况分析!
程序片段(07):收事件.c
内容概要:02.跨进程Event(收事件)
#include <stdio.h> #include <stdlib.h> #include <Windows.h> char name[100] = "haihualovefang"; int main(void) { //01.打开事件: // 获取跨进程所创建的事件 HANDLE myEvent = OpenEventA(EVENT_ALL_ACCESS, TRUE, name);//获取事件 if (NULL == myEvent) { printf("跨进程Event获取失败! \n"); system("pause"); return; } printf("跨进程Event等待状态! \n"); DWORD res = WaitForSingleObject(myEvent, 10000); switch (res) { case WAIT_OBJECT_0: printf("跨进程Event收到状态! \n"); break; case WAIT_TIMEOUT: printf("跨进程Event超时状态! \n"); break; case WAIT_ABANDONED: printf("另外一个进程已经中止! \n"); break; default: break; } CloseHandle(myEvent); system("pause"); }
程序片段(08):发信号.c
内容概要:01.跨进程Semaphore(发信号)
#include <stdio.h> #include <stdlib.h> #include <Windows.h> char name[100] = "haihualovefang"; int main(void) { HANDLE mySema = CreateSemaphoreA(NULL, 0, 1, name); printf("跨进程Semaphore信号量创建成功! \n"); char chr = getch(); ReleaseSemaphore(mySema, 1, NULL); printf("跨进程Semaphore发出信号! \n"); CloseHandle(mySema); system("pause"); } //01.当一条线程做完一件事情之后,需要通知其他线程的时候: // 这个时候就需要进行线程之间的通信 //注:区分线程通信与进程通信 //02.大数据你就得将图论和树结构玩儿的相当好才行 // 图和树就是用于管理这么多的线程的 //03.线程与线程之间的关系是很复杂的: // 需要掌握逻辑&排序&容错&模糊 //04.跨进程的线程通信: // Event&Mutex&Semaphore //05.使用跨进程通信的时候: // 1.最佳解决方案就是Mutex // 2.缺点比较: // Event&Semaphore:发信信号的进程关闭之后无法感知到! // Mutex:发送信号的进程关闭之后能够被感知到!
程序片段(09):收信号.c
内容概要:02.跨进程Semaphore(收信号)
#include <stdio.h> #include <stdlib.h> #include <Windows.h> char name[100] = "haihualovefang"; int main(void) { HANDLE mySema = OpenSemaphoreA(SEMAPHORE_ALL_ACCESS, TRUE, name); if (NULL == mySema) { printf("跨进程Semaphore创建失败! \n"); system("pause"); return; } printf("跨进程Semaphore等待状态! \n"); DWORD res = WaitForSingleObject(mySema, 10000); switch (res) { case WAIT_OBJECT_0: printf("跨进程Semaphore通信收到! \n"); break; case WAIT_TIMEOUT: printf("跨进程Semaphore通信超时! \n"); break; case WAIT_ABANDONED: printf("另外一个进程已经中止! \n"); break; default: break; } CloseHandle(mySema); system("pause"); }
程序片段(10):TimePrc.c
内容概要:时间同步
#include <stdio.h> #include <stdlib.h> #include <Windows.h> //01.时间同步:标准回调函数格式 // 1.小写"void"和大写"VOID"实质一样-->在这儿只是回调函数的规范 // 2.参数:普通指针+时间1[低点]+时间2[高点]-->相当于时差 // 3.创建一个回调函数格式的函数指针常量 // 4.回调函数:CALLBACK的标识定义(标准定义) VOID CALLBACK timeRun(void * pArg, DWORD timeLow, DWORD timeHigh) { DWORD dwindex = *(DWORD *)pArg; printf("第%d次! \n", dwindex); MessageBoxA(0, "1", "2", 0); } //02.Win操作系统之下使用系统自带的定时器资源: // 1.创建定时器:有几个函数-->起到等待作用的定时器 // 2.参数:arg1,arg2,arg3-->arg3是定时器的名称 // 3.匿名定时器只能有一个,携带名称的定时器可以有多个! int main(void) { HANDLE time1 = CreateWaitableTimerA(NULL, TRUE, "haihua"); if (NULL == time1) { printf("定时器创建失败! \n"); } //设置定时器特点 LARGE_INTEGER myTime; myTime.QuadPart = -50000000;//单位:0.1微妙--万分之一毫秒 //SetWaitTimer:定义解释 // _In_ HANDLE hTimer;定时器 // _In_ const LARGE_INTEGER * 1pDueTime;//时间 // _In_ LONG 1Period;//循环次数 // _In_opt_ PTIMERAPCROUTINE pfnCompletionRoutine;//函数指针 // _In_opt_ LPVOID 1pArgToCompletionRoutline;//参数 // _In_ BOOL fResume;//始终恢复状态 //设置等待的定时器(等待定时器) DWORD dwparam = 1; //1000说明1000毫秒-->1分钟干一次,回调间隔 if (SetWaitableTimer(time1, &myTime, 1000, timeRun, &dwparam, FALSE)) {//五秒钟之后触发该事件:1|0 printf("等待5秒之后开始干活儿! \n"); for (int i = 0; i < 15; ++i, ++dwparam) {//执行次数-->循环多少次,就回调多少次 SleepEx(INFINITE, TRUE); } } //循环完毕之后所需执行的操作: // 取消定时器和关闭句柄资源 CancelWaitableTimer(time1); CloseHandle(time1); if (WAIT_OBJECT_0 == WaitForSingleObject(time1, INFINITE)) {//等待消息成功 printf("wait ok! \n"); } else { printf("wait no! \n"); } system("pause"); } //01.多线程与队列: // 实现文件加密 //02.关于"时间定时器"的一些操作: // 简单定时器-->允许回调函数 //03.时间同步问题: // 1.主要用于解决多个线程的时间问题[多线程] // 2.围绕时间定时器,每隔一段事件干一定的活儿 // 3.满足一定的时间条件,然后解决一定的问题 //04.回调函数与时间的概念: // 1.触发函数的动作-->回调动作 // 2.执行完一段代码之后,执行某一个函数 //05.回调函数原理: // 1.数据1,2-->根据符号进行运算 // 2.整体函数[参数1+参数2+函数指针] // 3.定时器触发一个函数的执行 // 4.多个线程在同一时间要干某些事件 //06.同一个事件通知多个事件的执行 //07.回调函数:Callback // 1.函数指针,可以进行调用-->实现任何代码都可以进行自定义 // 2.整合功能:自定义功能以及它定义功能 //08.函数指针:新的功能-->函数指针-->功能随时更换 // 1.百度搜索原理 // 2.百度后台的搜索算法的改变 // 3.用户调用的时候会根据函数指针的区别[付钱状态,区域] //09.创建多个定时器需要根据名称进行区分 //10.定时器的使用步骤: // 1.创建定时器: // HANDLE time1=CreateWaitableTimerA(NULL,TRUE,"haihua"); // 2.五秒钟之后启动定时器: // LARGE_INTEGER mytime; // mytime.QuadPart=-50000000; // 3.定时器回调函数: // if(SetWaitableTimer(time1,&mytime,3000,timerun,&dwparam,FALSE)){} // 回调周期:3000毫秒之后循环一次-->循环多少次 //11.时间同步:主要用于游戏的开发 // 内核对象:游戏开发-->为了时间而单独编写一条线程不划算 // CreateWaitableTimerA();-->内核对象 // SetWaitableTimer();-->内核对象 // 内核对象-->多个时钟都有一个名称-->我就可以让多个线程同时读取一个时钟,进行操作,避免耗费资源
程序片段(11):
volatile.c
内容概要:Volatile
#include <stdio.h> #include <stdlib.h> #include <limits.h> void main01() { for (volatile int i = 0; i < INT_MAX; i++) {//(1)区分Debug模式和Release[+]模式 //(2)优化:强制读取内存 } system("pause"); } void main02() { volatile int i = 10; int a = i; printf("\ni=%d", a); //{偷偷改变i的值} _asm {//(1)_asm是汇编语言 //(2)修改数据-->栈底的内存是ebp,这里让其-4,也就是i这个栈底数据的改变 //(3)16进制的20h=32 //(4)这里的i已经不再寄存器里面,所以volatile强制读取内存当中经过修改之后的数据 //(5)ebp-4相当于栈底的指针:直接修改数据 mov dword ptr[ebp - 4], 20h; } int b = i; printf("\ni=%d", b); getchar(); } //01.Volatile强化: // 主要解决强制读取内存的动作 //02.Volatile原理:寄存器-内存 // 1.寄存器读取i的值,然后赋值给a,b // 2.当检测到i没有被修改的时候,读取寄存器中的i值 // 3.写入到a,b当中-->于是就缺少了读取内存的一次 // 4.只是读取了一次内存当中的i值 //03.数据被意外改变的情况之下经常使用Volatile // 数据意外改变-->编译器优化-->不读取内存[失误]
程序片段(12):Queue.h+Queue.c+数组顺序队列.c
内容概要:01.数组顺序队列
///Queue.h #pragma once #define DT int #define EN 100 typedef struct queue { int head; int tail; DT arr[EN]; } Queue; void initQueue(Queue * pQueue); int queueIsFull(Queue * pQueue); void enQueue(Queue * pQueue, DT data); int queueIsEmpty(Queue * pQueue); void showQueue(Queue * pQueue); DT queuGetHead(Queue * pQueue); void deQueue(Queue * pQueue);
///Queue.c #include "Queue.h" #include <stdlib.h> #include <memory.h> void initQueue(Queue * pQueue) { if (NULL == pQueue) abort(); memset((*pQueue).arr, 0, EN * sizeof(DT)); (*pQueue).tail = (*pQueue).head = 0; } int queueIsFull(Queue * pQueue) { if (NULL == pQueue) abort(); if (EN == (*pQueue).tail) return 1; return 0; } void enQueue(Queue * pQueue, DT data) { if (NULL == pQueue) abort(); if (queueIsFull(pQueue)) return; (*pQueue).arr[(*pQueue).tail] = data; ++(*pQueue).tail; } int queueIsEmpty(Queue * pQueue) { if (NULL == pQueue) abort(); if ((*pQueue).head == (*pQueue).tail) return 1; return 0; } void showQueue(Queue * pQueue) { if (NULL == pQueue) abort(); if (queueIsEmpty(pQueue)) return; for (int i = 0; i < (*pQueue).tail; ++i) { printf("%3d", (*pQueue).arr[i]); } printf("\n"); } DT queueGetHead(Queue * pQueue) { if (NULL == pQueue) abort(); if (queueIsEmpty(pQueue)) return; return (*pQueue).arr[0]; } //01.几种特殊数据结构的实现方式: // 1.栈结构: // 数组栈:tail-=1(无所谓正向和反向) // 链表栈: // 正向:尾部增加,尾部减少 // 反向:头部增加,头部减少 // 2.队列结构: // 数组队列:正反向的效率一致 // 链表队列: // 正向:尾部增加,头部减少 // 反向:头部增加,尾部减少 //注:数组队列,存在明显缺点,需要进行内存移动! // 队列的损耗,移动费时费力! //注:解决数组移动移动费时费力的方案: // 改造成环数组形队列+改造成链表队列 void deQueue(Queue * pQueue) { if (NULL == pQueue) abort(); if (queueIsEmpty(pQueue)) return; for (int i = 0; i < (*pQueue).tail - 1; ++i) { (*pQueue).arr[i] = (*pQueue).arr[i + 1]; } --(*pQueue).tail; }
///数组顺序队列.c #include "Queue.h" #include <stdio.h> #include <stdlib.h> int main01(void) { Queue queue; initQueue(&queue); for (int i = 0; i < 10; ++i) { enQueue(&queue, i); } showQueue(&queue); while (!queueIsEmpty(&queue)) { printf("出队的数据是%3d \n", queueGetHead(&queue)); deQueue(&queue); showQueue(&queue); } system("pause"); } //01.顺序队列:逻辑编程 // 工厂模式+(生产者-消费者)模式+请求响应模式 //02.生产者与消费者: // 1.生产线程(生产者) // 2.消费线程(消费者) // 3.库存情况:库存越少越好,但是不能断掉供应链 // 队列关系:生产者生产,消费者消费 // 顺序关系:先进先出特点(存在顺序) // 原理:队列&多线程--请求|响应模式 //03.三种队列的实现: // 1.数组顺序队列(尾部插入,头部取出) // 2.数组环形顺序队列(尾部插入,头部取出) // 2.链表反向队列(头部插入,尾部取出) // 队列实现:基础之上实现 // (生产者&消费者)模式 // (发送消息&接收消息)的模式 // (请求&响应)模式 //04.数组顺序队列-->数组环形顺序队列 // 单链表-->双链表:单独的结构-->追求快一点儿,从简 //05.环形队列原理: // 1.吃东西-->拉东西 // 2.吃:前面+,拉:往前走 // 3.吃的太多,重合情况(特殊情况) //06.环形队列解释: // 1.头尾重合,没有数据,添加一个数据之后,头不变,尾向后移一个结构体单位 // 2.顺序队列的缺点:删除的时候移动很累(数组环形队列可以解决这个问题) //07.顺序队列解释: // 1.头部必须固定 // 2.移动费时费力 //08.队列移动问题的改造: // 1.链表结构 // 2.环形队列
程序片段(13):CircleQueue.h+CircleQueue.c+数组正向环形队列.c
内容概要:02.数组环形顺序队列
///CircleQueue.h #pragma once #define DT int #define EN 0 typedef struct circleQueue { int head; int tail; DT arr[EN]; } CircleQueue; void initCircleQueue(CircleQueue * pCircleQueue); int circleQueueIsFull(CircleQueue * pCircleQueue); void enCircleQueue(CircleQueue * pCircleQueue, DT data); int circleQueueIsEmpty(CircleQueue * pCircleQueue); void showCircleQueue(CircleQueue * pCircleQueue); DT circleQueueGetHead(CircleQueue * pCircleQueue); void deCircleQueue(CircleQueue * pCircleQueue);
///CircleQueue.c #include "CircleQueue.h" #include <stdlib.h> #include <memory.h> void initCircleQueue(CircleQueue * pCircleQueue) { if (NULL == pCircleQueue) abort(); memset((*pCircleQueue).arr, 0, EN * sizeof(DT)); (*pCircleQueue).tail = (*pCircleQueue).head = 0; } //01.如何判断环形队列是否装满元素? // 1.这儿有三种特殊情况需要考虑: // 头部+中部+尾部 // 2.最终可归结为两种环形队列满元素的情况: // 头部+中部(尾部和头部合并) // 3.归纳总结: // 头尾+循环情况 int circleQueueIsFull(CircleQueue * pCircleQueue) { if (NULL == pCircleQueue) abort(); if ((*pCircleQueue).head == ((*pCircleQueue).tail + 1) % EN) { return 1; } return 0; } void enCircleQueue(CircleQueue * pCircleQueue, DT data) { if (NULL == pCircleQueue) abort(); if (circleQueueIsFull(pCircleQueue)) return; (*pCircleQueue).arr[(*pCircleQueue).tail] = data; (*pCircleQueue).tail = ((*pCircleQueue).tail + 1) % EN; } int circleQueueIsEmpty(CircleQueue * pCircleQueue) { if (NULL == pCircleQueue) abort(); if ((*pCircleQueue).head == (*pCircleQueue).tail) return 1; return 0; }
///数组正向环形队列.c //01.数组顺序环形队列的思想: // 1.就是把数组当成闭合顺序环形队列(数组-->抽象-->环形队列) // 2.思想演示: // (1).原型:1 2 3 4 5 6 7 8 9 10 // rear front // (2).抽离:8 9 10 1 2 // 原理:普通数组抽象为顺序环形队列 // 1).front-->rear:两个指针轮询移动 // 2).防止front和rear:都走到头的情况 // 3).节约移动情况(环形队列的优点) //02.环形队列实现: // 数组法+链表法 // 顺序法+逆序法 //03.环形队列的应用场景: // 操作系统对线程的管理这块儿 //04.环形队列的两种情况: // 头尾情况+中部情况 //05.环形队列:情况分析 // 头尾+中部最终利用一个表达式进行表示 //06.环形链表: // 1.rear说明了元素的个数 // front=0&rear=5的情况 // 2.rear重合情况二 // 3.一般情况之下,要是想实现环形队列,数组或者链表都 // 需要空出一个位置,防止front&rear重合 //07.环形链表规则指定: // 1.为空:避免重合和满了的情况一致 // 2.rear+1%5的特点-->代表存储继续前进 // 3.满的情况综合: // (rear+1)%5==front说明重合装满
程序片段(15):CircleQueue.h+CircleQueue.h+数组正向环形队列.c
内容概要:01.数组正向环形队列
///CircleQueue.h #pragma once #define DT int #define EN 10 //01.数组正向环形队列: // 优点:出队一个元素,无需进行队列数组元素的整体移动 // 特点:如果模拟数组的长度为N // 普通队列:需要使用到N个元素 // 环形队列:需要使用到N-1个元素 //注:留出一个空位是为了区分队列重合情况和队列满载情况 // 普通重合情况:就是空队列 // 特殊重合情况:就是满队列 typedef struct circleQueue { DT arr[EN]; int head; int tail; }CircleQueue; void initCircleQueue(CircleQueue * pCircleQueue); int circleQueueIsFull(CircleQueue * pCircleQueue); void enCircleQueue(CircleQueue * pCircleQueue, DT data); int circleQueueIsEmpty(CircleQueue * pCircleQueue); void showCircleQueue(CircleQueue * pCircleQueue); DT circleQueueGetHead(CircleQueue * pCircleQueue); void deCircleQueue(CircleQueue * pCircleQueue);
///CircleQueue.c #include "CircleQueue.h" #include <stdlib.h> #include <memory.h> #include <stdio.h> void initCircleQueue(CircleQueue * pCircleQueue) { if (NULL == pCircleQueue) abort(); memset((*pCircleQueue).arr, 0, EN * sizeof(DT)); (*pCircleQueue).tail = (*pCircleQueue).head = 0; } int circleQueueIsFull(CircleQueue * pCircleQueue) { if (NULL == pCircleQueue) abort(); if ((*pCircleQueue).head == ((*pCircleQueue).tail + 1) % EN) return 1; return 0; } //01.空位主要的作用: // 1.为了缓冲末尾位置可以进行循环填充数据! // 2.为了可以准确区分环形队列的两种情况: // 空队列+满队列 //注:还可以确定最后一个入队的元素到底应当放置于何处! void enCircleQueue(CircleQueue * pCircleQueue, DT data) { if (NULL == pCircleQueue) abort(); if (circleQueueIsFull(pCircleQueue)) return; (*pCircleQueue).arr[(*pCircleQueue).tail] = data; (*pCircleQueue).tail = ((*pCircleQueue).tail + 1) % EN; } int circleQueueIsEmpty(CircleQueue * pCircleQueue) { if (NULL == pCircleQueue) abort(); if ((*pCircleQueue).head == (*pCircleQueue).tail) return 1; return 0; } void showCircleQueue(CircleQueue * pCircleQueue) { if (NULL == pCircleQueue) abort(); if (circleQueueIsEmpty(pCircleQueue)) return; //环形队列:元素不确定+起点不确定(无法确定循环次数) int i = (*pCircleQueue).head; int count = 0; do { printf("%3d", (*pCircleQueue).arr[(i++) % EN]); if (9 == ++count) break; } while ((((*pCircleQueue).tail + 1) % EN != i % EN) && (i %EN < (*pCircleQueue).tail)); printf("\n"); } DT circleQueueGetHead(CircleQueue * pCircleQueue) { if (NULL == pCircleQueue) abort(); if (circleQueueIsEmpty(pCircleQueue)) return -1; return (*pCircleQueue).arr[(*pCircleQueue).head]; } void deCircleQueue(CircleQueue * pCircleQueue) { if (NULL == pCircleQueue) abort(); if (circleQueueIsEmpty(pCircleQueue)) return; (*pCircleQueue).head = ((*pCircleQueue).head + 1) % EN; }
///数组正向环形队列.c #include "CircleQueue.h" #include <stdio.h> #include <stdlib.h> int main01(void) { CircleQueue circleQueue; initCircleQueue(&circleQueue); for (int i = 0; i < 9; ++i) { enCircleQueue(&circleQueue, i + 1); showCircleQueue(&circleQueue); } while (!circleQueueIsEmpty(&circleQueue)) { printf("数组正向环形队列出队:%3d \n", circleQueueGetHead(&circleQueue)); deCircleQueue(&circleQueue); showCircleQueue(&circleQueue); } system("pause"); }
程序片段(16):Queue.h+Queue.c+数组正向环形队列.c
内容概要:02.数组正向环形队列(标准版)
///Queue.h #pragma once #define DT int #define EN 10 //01.采用数组模拟队列的两种特点: // 1.假设待用于模拟的数组共有N个元素 // 2.两种目标队列模型: // 普通队列:数组正向队列,使用N个元素 // 环形队列:数组正向环形队列,使用N-1个元素 //注:环形队列,删除一个元素便无需移动 typedef struct queue { DT arr[EN]; int head; int tail; }Queue; void initQueue(Queue * pQueue); int queueIsFull(Queue * pQueue); void enQueue(Queue * pQueue, DT data); int queueIsEmpty(Queue * pQueue); void showQueue(Queue * pQueue); DT queueGetHead(Queue * pQueue); void deQueue(Queue * pQueue);
///Queue.c #include "Queue.h" #include <stdlib.h> #include <memory.h> #include <stdio.h> void initQueue(Queue * pQueue) { if (NULL == pQueue) abort(); memset((*pQueue).arr, 0, EN * sizeof(DT)); (*pQueue).tail = (*pQueue).head = 0; } //01.区分:数组正向环形队列的两种情况 // 1.空队列:起始位置=终止位置 // 2.满队列:起始位置=(终止位置+1)%EN; //注:关于环形队列的面试填空问题 // 1.预留一个空数组元素用作这两种情况的区分 // 空队列和满队列的准确区分 // 2.使得环形队列的循环利用情况得到维持 // 能够循环利用到环形队列当中的每个元素位置 // 3.极端情况分析: // (1).头尾:head<tail // (2).中间:head>tail // (3).相同:head=tail int queueIsFull(Queue * pQueue) { if (NULL == pQueue) abort(); if ((*pQueue).head == ((*pQueue).tail + 1) % EN) { return 1; } return 0; } //02.数组正向环形队列的入队比数组正向队列麻烦多了: // 1.特点就是:始终在模拟正向环形队列的数组当中空余一个元素位置 // 用作区分空队列和满队列以及维持环形队列的循环状况 // 2.走环形的特点!充分利用取余运算符的特点 //注:取余运算符能够杜绝两种特殊情况: // 起点刚好冲数组首位置开始的情况 // 起点不是位于数组首位置的情况 // 特:在这两种情况之下都能够维持空余一个元素位置的特点 //最后一个位置无论何种情况都不会被使用到! void enQueue(Queue * pQueue, DT data) { if (NULL == pQueue) abort(); if (queueIsFull(pQueue)) return; (*pQueue).arr[(*pQueue).tail] = data; (*pQueue).tail = ((*pQueue).tail + 1) % EN;//就是为了一定要空余最后一个位置 } //03.空队列的两种情况: // 重合点为:(起点位置or终点位置) // 重合点为:模拟数组的任何位置! //注:实质上就是两点重合! int queueIsEmpty(Queue * pQueue) { if (NULL == pQueue) abort(); if ((*pQueue).head == (*pQueue).tail) return 1; return 0; } void showQueue(Queue * pQueue) { if (NULL == pQueue) abort(); for (int i = (*pQueue).head; i % EN < (*pQueue).tail ; ++i) { printf("%3d", (*pQueue).arr[i]); } printf("\n"); } DT queueGetHead(Queue * pQueue) { if (NULL == pQueue) abort(); if (queueIsEmpty(pQueue)) return; return (*pQueue).arr[(*pQueue).head]; } void deQueue(Queue * pQueue) { if (NULL == pQueue) abort(); if (queueIsEmpty(pQueue)) return; (*pQueue).head = ((*pQueue).head + 1) % EN; }
///数组正向环形队列.c #include "Queue.h" #include <stdio.h> #include <stdlib.h> int main02(void) { //for (int i = 0;;++i) //{ // printf("%2d", i %10); //} Queue queue; initQueue(&queue); for (int i = 0; i < EN - 1; ++i) { enQueue(&queue, i + 1); showQueue(&queue); } while (!queueIsEmpty(&queue)) { printf("%3d", queueGetHead(&queue)); deQueue(&queue); } system("pause"); } //01.环形队列: // 1.最后一个坑用于表示模拟结束:标识结束 // 标识结束+区分空队列和满队列+可循环利用 // 2.环形队列原理深究: // 环形队列的优先级问题-->顺序队列同样有 //注:优先队列 //02.环形队列的应用: // 1.高效应用 // 2.操作系统在一段时间之内只能运行一个线程 //03.操作系统的特点: // 1.我一段时间限定内只能运行一段儿程序,所以操作系统 // 为每一条线程分配相应的时间片,然后获取时间片之后 // 就开始执行-->操作系统1秒钟有1000次夺回控制权 // 2.Windows属于抢占式操作系统 // 操作系统时时刻刻夺回控制权,在重新进行分配 // 3.冻结状态与解冻状态的体现 //04.处理队列的时候需要将数据更替为HANDLE类型 //05.使用数组构建环形队列比使用链表构建环形队列简单多了 //06.数组正向环形队列相比数组正向队列的好处: // 删除一个元素之后不需要进行移动,消耗效率
程序片段(17):CircleQueue.h+CircleQueue.c+数组正向环形队列.c
内容概要:01.数组正向环形队列
///CircleQueue.h #pragma once #define DT int #define EN 10 //01.数组模拟队列: // 普通队列:使用N个数组元素 // 环形队列:使用N-1个数组元素 typedef struct circleQueue { DT arr[EN]; int head; int tail; }CircleQueue; void initCircleQueue(CircleQueue * pCircleQueue); int circleQueueIsFull(CircleQueue * pCircleQueue); void enCircleQueue(CircleQueue * pCircleQueue, DT data); int circleQueueIsEmpty(CircleQueue * pCircleQueue); void showCircleQueue(CircleQueue * pCircleQueue); DT circleQueueGetHead(CircleQueue * pCircleQueue); void deCircleQueue(CircleQueue * pCircleQueue);
///CircleQueue.c #include "CircleQueue.h" #include <stdlib.h> #include <memory.h> #include <stdio.h> void initCircleQueue(CircleQueue * pCircleQueue) { if (NULL == pCircleQueue) abort(); memset((*pCircleQueue).arr, 0, EN * sizeof(DT)); (*pCircleQueue).tail = (*pCircleQueue).head = 0; } int circleQueueIsFull(CircleQueue * pCircleQueue) { if (NULL == pCircleQueue) abort(); if ((*pCircleQueue).head == ((*pCircleQueue).tail + 1) % EN)//满队列 return 1; return 0; } void enCircleQueue(CircleQueue * pCircleQueue, DT data) { if (NULL == pCircleQueue) abort(); if (circleQueueIsFull(pCircleQueue)) return; (*pCircleQueue).arr[(*pCircleQueue).tail] = data;//当前填充位置 (*pCircleQueue).tail = ((*pCircleQueue).tail + 1) % EN;//下个填充位置+保证连续存储 } int circleQueueIsEmpty(CircleQueue * pCircleQueue) { if (NULL == pCircleQueue) abort(); if ((*pCircleQueue).head == (*pCircleQueue).tail)//空队列 return 1; return 0; } void showCircleQueue(CircleQueue * pCircleQueue) { if (NULL == pCircleQueue) abort(); if (circleQueueIsEmpty(pCircleQueue)) return; for (int i = (*pCircleQueue).head; i%EN < (*pCircleQueue).tail; ++i)//i<=>i%EN:这里是环形队列没有出现特殊情况的特点! {//数组正向环形队列:1.不确定数组环形队列元素个数+2.不确定环形队列的起始元素和终止元素位置(因此打印无法控制) printf("%3d", (*pCircleQueue).arr[i]); } printf("\n"); } DT circleQueueGetHead(CircleQueue * pCircleQueue) { if (NULL == pCircleQueue) abort(); if (circleQueueIsEmpty(pCircleQueue)) return -1; return (*pCircleQueue).arr[(*pCircleQueue).head]; } void deCircleQueue(CircleQueue * pCircleQueue) { if (NULL == pCircleQueue) abort(); if (circleQueueIsEmpty(pCircleQueue)) return; (*pCircleQueue).head = ((*pCircleQueue).head + 1) % EN; }
///数组正向环形队列.c #include "CircleQueue.h" #include <stdio.h> #include <stdlib.h> #include <Windows.h> int main01(void) { CircleQueue circleQueue = { 0 }; initCircleQueue(&circleQueue); for (int i = 0; i < 9; ++i) { enCircleQueue(&circleQueue, i + 1); showCircleQueue(&circleQueue); } while (!circleQueueIsEmpty(&circleQueue)) { printf("%3d \n", circleQueueGetHead(&circleQueue)); deCircleQueue(&circleQueue); showCircleQueue(&circleQueue); } system("pause"); } CircleQueue circleQueue = { 0 }; DWORD WINAPI producer(void * p) { printf("生产者第01次执行生产任务! \n"); int data = 0; while (!circleQueueIsFull(&circleQueue)) {//生产者:一次性补充满库存黁量 enCircleQueue(&circleQueue, ++data); printf("生产者生产了%3d! \n", data); } Sleep(1000); HANDLE event1 = OpenEvent(EVENT_ALL_ACCESS, TRUE, L"producer"); SetEvent(event1); int i = 1; while (++i) { HANDLE event2 = OpenEvent(EVENT_ALL_ACCESS, TRUE, L"consumer"); WaitForSingleObject(event2, INFINITE); printf("生产者第%02d次执行生产任务! \n", i); while (!circleQueueIsFull(&circleQueue)) { enCircleQueue(&circleQueue, ++data); printf("生产者生产了%3d! \n", data); } Sleep(1000); SetEvent(event1); } return 0; } DWORD WINAPI consumer(void * p) { int i = 0; while (++i) { HANDLE event1 = OpenEvent(EVENT_ALL_ACCESS, TRUE, L"producer"); WaitForSingleObject(event1, INFINITE); printf("消费者第%02d次执行消费任务! \n", i); int num = 9; for (int j = 0; j < num; ++j) { if (!circleQueueIsEmpty(&circleQueue)) { printf("消费者消费了%3d! \n", circleQueueGetHead(&circleQueue)); deCircleQueue(&circleQueue); } } Sleep(1000); HANDLE event2 = OpenEvent(EVENT_ALL_ACCESS, TRUE, L"consumer"); SetEvent(event2); } return 0; } int main02(void) { HANDLE event1 = CreateEvent(NULL, FALSE, FALSE, L"producer"); HANDLE event2 = CreateEvent(NULL, FALSE, FALSE, L"consumer"); HANDLE threadArr[2] = { 0 }; threadArr[0] = CreateThread(NULL, 0, producer, NULL, 0, NULL); threadArr[1] = CreateThread(NULL, 0, consumer, NULL, 0, NULL); //WaitForSingleObject(producer, INFINITE);//可以直接等待单个线程任务执行结束以后! WaitForMultipleObjects(2, threadArr, TRUE, INFINITE); CloseHandle(event1); system("pause"); } //01.消费者不管买走多少,都需要将存储结构塞满 // 紧缺产品:针对于畅销产品的库存解决方案 // 随时保持库存充足 // 停滞产品:针对于停滞产品的库存解决方案 // 在满足市场供需的情况之下,库存越少越好 //02.Scanf不是一个线程安全的函数 // 1.所以需要手动进行安全检查 // 2.它也是系统出现漏洞的原因之一 //03.防止进栈压栈冲突:延迟 // 互锁:不要让生产者边生产而消费者边消费 // 解决:生产者完成之后消费者进行消费 //注:以上情况不符合现实情况,现实情况之下需要解决多线程异步并发访问冲突问题 //04.生产者&消费者: // 1.环形队列的仓库,保证这个库存-->生产的是紧缺产品(随时满足库存量) // 2.库存一定需要填满(针对于畅销紧缺产品) //05.工厂设计模式: // 1.同时生产多个产品-->产品&线程开辟-->平衡调度线程 // 工厂:多线程 // 2.前台卖货:平衡调度 // 库存控制,畅销与非畅销 // 3.消费者消费:千变万化 //注:区分(生产者与消费者)和(工厂)两种设计模式的区别: // 生产者与消费者:单产品 // 工厂:多产品 //06.链式队列(无线)&栈(有限) // 服务器几十万几百万的多线程操作 //07.内存数据库: // 1.所有数据都载入内存-->发出请求 // 2.文件载入内存 // 3.消费者提出(需求),生产者进行(生产) // 4.线程不断的进行加载 // 5.防止多线程并发访问 // 6.迁移到CGI: 手机查询 // 7.多线程与队列问题-->稳定与不稳定
程序片段(18):Queue.h+Queue.c+Main.c
内容概要:02.链表反向队列
///Queue.h #pragma once #define DT int typedef struct node { DT data; struct node * pNext; }Node; void initQueue(Node ** ppQueue); void enQueue(Node ** ppQueue, DT data); void showQueue(Node * pQueue); DT queueGetHead(Node * pQueue); void deQueue(Node ** ppQueue);
///Queue.c #include "Queue.h" #include <stdlib.h> #include <stdio.h> void initQueue(Node ** ppQueue) { if (NULL == ppQueue) abort(); *ppQueue = NULL; } void enQueue(Node ** ppQueue, DT data) { if (NULL == ppQueue)//无队列 abort(); Node * pNew = (Node *)malloc(sizeof(Node)); pNew->data = data; pNew->pNext = NULL; if (NULL == *ppQueue)//空队列 { *ppQueue = pNew; return; } pNew->pNext = *ppQueue; *ppQueue = pNew; } void showQueue(Node * pQueue) { if (NULL == pQueue) return; for (Node * pTmp = pQueue; NULL != pTmp; pTmp = pTmp->pNext) { printf("%3d", pTmp->data); } printf("\n"); } DT queueGetHead(Node * pQueue) { if (NULL == pQueue) abort(); Node * pTmp = pQueue; while (NULL != pTmp->pNext) { pTmp = pTmp->pNext; } return pTmp->data; } void deQueue(Node ** ppQueue) { if (NULL == ppQueue) abort(); if (NULL == *ppQueue) return; if (NULL == (*ppQueue)->pNext) { free(*ppQueue); *ppQueue = NULL; return; } Node * pTmp = *ppQueue; while (NULL != pTmp->pNext->pNext) { pTmp = pTmp->pNext; } free(pTmp->pNext); pTmp->pNext = NULL; }
///Main.c #include "Queue.h" #include <stdlib.h> #include <Windows.h> //01.链表反向队列: // 全局变量:用作跨线程通信变量 Node * pQueue = NULL; //02.生产者消费者模式之生产者: // 1.时时刻刻盯着链表反向队列结构 // 2.区分:畅销产品与非畅销产品 //注:避免过度消耗资源的情况发生 DWORD WINAPI producer(void * p) {//非畅销产品 int i = 0; while (++i) { if (NULL == pQueue) { enQueue(&pQueue, i); printf("生产者生产了产品%3d! \n", i); } Sleep(1000); } return 0; } DWORD WINAPI consumer(void * p) { int i = 0; while (++i) { MessageBoxA(0, "wait", "consumer", 0); printf("消费者消费了%3d! \n", queueGetHead(pQueue)); deQueue(&pQueue); } return 0; } int main01(void) { HANDLE threadArr[2] = { 0 }; threadArr[0] = CreateThread(NULL, 0, producer, NULL, 0, NULL); threadArr[1] = CreateThread(NULL, 0, consumer, NULL, 0, NULL); WaitForMultipleObjects(2, threadArr, TRUE, INFINITE); system("pause"); } //01.生产者与消费者(设计模式): // 1.链表反向队列:作为流水线 // 数组(正向&反向)队列&数组(正向&反向)环形队列&链表(正向&反向)队列 // 2.线程结构:生产者&消费者 // 3.流程原理: // (1).当流水线为空的时候,生产者生产 // (2).生产者:非畅销&畅销(视具体情况而定) // (3).消费者:手动控制,可以获取任意个数 // 设计模式:看不明白的主要原因是因为多线程 // 单线程没有意义,多线程才有意义 //02.生产者&消费者: // 1.生产"紧缺"产品&生产"非紧缺"产品 // 2.生产者&消费者所做事情: // (1),生产者时时刻刻检测数据结构是否已经填充满了 // 没有满需要插入数据-->链式队列:锁定数目就行了(防止无限仓库产生) // 理论上都不推荐使用链式队列:因为过渡消耗资源 // -->链式栈不存在满的情况:可以进行无限拓展 // (2).用于软件开发的两种情况: // 1).生产&消费分开做 // 2).工厂模式更加复杂(不同类型的生产者与消费者模式) //03.理解生产者与消费者 // 1.生产者需要保证至少有一个 // 2.消费者的消费情况是随机消费的 // 3.消费者需要配合生产着 // 一个入队,一个出队[消费者的消费是个不确定的数据] // 4.线程通信中间使用最多的是什么? // 事件&互斥量&信号量
程序片段(19):Queue.h+Queue.c+01.Event通信(生产者消费者).cpp+02.Semaphore通信(生产者消费者).c
内容概要:03.生产者与消费者模式
///Queue.h #pragma once #define DT int typedef struct node { DT data; struct node * pNext; }Node; void initQueue(Node ** ppQueue); void enQueue(Node ** ppQueue, DT data); void showQueue(Node * pQueue); DT queueGetHead(Node * pQueue); void deQueue(Node ** ppQueue);
///Queue.c #include "Queue.h" #include <stdlib.h> #include <stdio.h> void initQueue(Node ** ppQueue) { if (NULL == ppQueue) abort(); *ppQueue = NULL; } void enQueue(Node ** ppQueue, DT data) { if (NULL == ppQueue)//无队列 abort(); Node * pNew = (Node *)malloc(sizeof(Node)); pNew->data = data; pNew->pNext = NULL; if (NULL == *ppQueue)//空队列 { *ppQueue = pNew; return; } pNew->pNext = *ppQueue; *ppQueue = pNew; } void showQueue(Node * pQueue) { if (NULL == pQueue) return; for (Node * pTmp = pQueue; NULL != pTmp; pTmp = pTmp->pNext) { printf("%3d", pTmp->data); } printf("\n"); } DT queueGetHead(Node * pQueue) { if (NULL == pQueue) abort(); Node * pTmp = pQueue; while (NULL != pTmp->pNext) { pTmp = pTmp->pNext; } return pTmp->data; } void deQueue(Node ** ppQueue) { if (NULL == ppQueue) abort(); if (NULL == *ppQueue) return; if (NULL == (*ppQueue)->pNext) { free(*ppQueue); *ppQueue = NULL; return; } Node * pTmp = *ppQueue; while (NULL != pTmp->pNext->pNext) { pTmp = pTmp->pNext; } free(pTmp->pNext); pTmp->pNext = NULL; }
///01.Event通信(生产者消费者).cpp #include "Queue.h" #include <stdio.h> #include <stdlib.h> #include <Windows.h> Node * pQueue = NULL; DWORD WINAPI producer(void * p) { enQueue(&pQueue, 1); int i = 1; while (++i) { HANDLE event1 = OpenEvent(EVENT_ALL_ACCESS, TRUE, L"consumer"); WaitForSingleObject(event1, INFINITE); printf("生产者生产了%3d! \n", i); enQueue(&pQueue, i); } return 0; } DWORD WINAPI consumer(void * p) { int i = 0; while (++i) { MessageBoxA(0, "wait", "wait", 0); printf("消费者消费了%3d! \n", queueGetHead(pQueue)); deQueue(&pQueue); HANDLE event1 = OpenEvent(EVENT_ALL_ACCESS, TRUE, L"consumer"); SetEvent(event1); } return 0; } int main01(void) { HANDLE event1 = CreateEvent(NULL, FALSE, FALSE, L"consumer"); HANDLE threadArr[2] = { 0 }; threadArr[0] = CreateThread(NULL, 0, producer, NULL, 0, NULL); threadArr[1] = CreateThread(NULL, 0, consumer, NULL, 0, NULL); WaitForMultipleObjects(2, threadArr, TRUE, INFINITE); CloseHandle(event1); system("pause"); return 1; } //01.消费完成之后设置事件的触发 //02.每秒钟进行检测,浪费资源 //03.事件的关键步骤: // CloseHandle(event); //04.在一个线程里面不需要死循环: // 因为它在这儿i不断的进行自增,增加的次数不确定 //05.事件通信&信号量通信
///02.Semaphore通信(生产者消费者).c #include "Queue.h" #include <stdio.h> #include <stdlib.h> #include <Windows.h> Node * pQueue = NULL; DWORD WINAPI producer(void * p) { HANDLE sema = OpenSemaphoreA(SEMAPHORE_ALL_ACCESS, TRUE, "consumer"); enQueue(&pQueue, 1); int i = 1; while (++i) { WaitForSingleObject(sema, INFINITE); printf("生产者生产了%3d! \n", i); enQueue(&pQueue, i); } return 0; } DWORD WINAPI consumer(void * p) { HANDLE sema = OpenSemaphoreA(SEMAPHORE_ALL_ACCESS, TRUE, "consumer"); int i = 0; while (++i) { MessageBoxA(0, "wait", "consumer", 0); printf("消费者消费了%3d! \n", queueGetHead(pQueue)); deQueue(&pQueue); ReleaseSemaphore(sema, 1, NULL); } return 0; } int main02(void) { HANDLE sema = CreateSemaphoreA(NULL, 0, 1, "consumer"); HANDLE threadArr[2] = { 0 }; threadArr[0] = CreateThread(NULL, 0, producer, NULL, 0, NULL); threadArr[1] = CreateThread(NULL, 0, consumer, NULL, 0, NULL); WaitForMultipleObjects(2, threadArr, TRUE, INFINITE); CloseHandle(sema); system("pause"); } //01.信号量解决生产者与消费者问题: // C++称之为工厂设计模式 //02.事件-->互斥量解决线程通信问题: // 事件-->信号量问题分析 //03.设计模式结合多线程比较好理解 // 两个变量之间的双方通信规则
相关文章推荐
- 20160227.CCPP体系详解(0037天)
- C++作业2
- array数组容器 C++
- 20160227.CCPP体系详解(0037天)
- C语言typedef的用法
- 约瑟夫环问题——(Hdu-5643,King's Game)
- 智能指针(现代 C++)
- C语言(对内存的理解)
- [面试] C/C++ —— MFC(三)
- C++11中使用shared_ptr和unique_ptr管理动态数组
- C/C++程序题(11-15)
- c++第二次实验-1
- CodeForces 635A Orchestra
- C语言指针的指针作用(返回值)之二
- C++中map容器的使用
- [面试] C/C++ —— MFC(二)
- C++中vector的remove用法
- C++学习笔记——第12章 继承
- C++模板堆排序
- 标准C++中的string类的用法总结