20160227.CCPP體系詳解(0037天)
程式片段(01):01.一對一模式.c+02.中介者模式.c+03.廣播模式.c
內容概要:事件
///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.設計模式結合多執行緒比較好理解
// 兩個變數之間的雙方通訊規則
相關文章
- 20160217.CCPP體系詳解(0027天)
- 20160124.CCPP詳解體系(0003天)
- 20160125.CCPP詳解體系(0004天)
- 20160126.CCPP體系詳解(0005天)
- 20160127.CCPP體系詳解(0006天)
- 20160130.CCPP體系詳解(0009天)
- 20160203.CCPP體系詳解(0013天)
- 20160211.CCPP體系詳解(0021天)
- 20160213.CCPP體系詳解(0023天)
- 20160214.CCPP體系詳解(0024天)
- 20160215.CCPP體系詳解(0025天)
- 20160224.CCPP體系詳解(0034天)
- 20160218.CCPP體系詳解(0028天)
- 20160219.CCPP體系詳解(0029天)
- 手遊《天地劫》的三天體驗——深度系統剖析及玩法詳解
- 20160122.CCPP詳解體系(0001天)
- 20160123.CCPP詳解體系(0002天)
- 20160128.CCPP體系詳解(0007天)
- 20160129.CCPP體系詳解(0008天)
- 20160131.CCPP體系詳解(0010天)
- 20160204.CCPP體系詳解(0014天)
- 20160205.CCPP體系詳解(0015天)
- 20160210.CCPP體系詳解(0020天)
- 20160212.CCPP體系詳解(0022天)
- 20160207.CCPP體系詳解(0017天)
- 20160225.CCPP體系詳解(0035天)
- 20160226.CCPP體系詳解(0036天)
- 20160222.CCPP體系詳解(0032天)
- 20160221.CCPP體系詳解(0031天)
- 20160201.CCPP體系詳解(0011天)
- 20160202.CCPP體系詳解(0012天)
- 20160209.CCPP體系詳解(0019天)
- 20160216.CCPP體系詳解(0026天)
- 20160206.CCPP體系詳解(0016天)
- 20160208.CCPP體系詳解(0018天)
- 20160223.CCPP體系詳解(0033天)
- 20160220.CCPP體系詳解(0030天)
- MySQL體系結構詳解MySql