网站建设应具备的技能,城乡建设部门户网站,自己设计房子装修app免费,做网站要多少钱联系方式目录
一、基本概念
二、线程创建函数
三、Windows内核对象与句柄
四、简单的多线程案例
五、线程同步 - 互斥对象
六、多线程实现群聊的服务端和客户端
七、线程同步 - 事件对象
八、事件对象 与 互斥对象区别
九、线程同步 - 信号量
十、线程同步 - 关键代码段
十一…目录
一、基本概念
二、线程创建函数
三、Windows内核对象与句柄
四、简单的多线程案例
五、线程同步 - 互斥对象
六、多线程实现群聊的服务端和客户端
七、线程同步 - 事件对象
八、事件对象 与 互斥对象区别
九、线程同步 - 信号量
十、线程同步 - 关键代码段
十一、线程死锁
十二、四种线程同步方式总结 一、基本概念
线程是在进程中产生的一个执行单元是 CPU 调度和分配的最小单元其在同一个进程中与其他线程并行运行他们可以共享进程内的资源比如内存、地址空间、打开的文件等等。
进程正在运行的程序 --- 狭义是处于执行期的程序以及它所管理的资源如打开的文件、挂起的信号、进程状态、地址空间等等的总称从操作系统核心角度来说进程是操作系统调度除CPU 时间片外进行的资源分配和保护的基本单位它有一个独立的虚拟地址空间用来容纳进程映像(如与进程关联的程序与数据)并以进程为单位对各种资源实施保护如受保护地访问处理器、文件、外部设备及其他进程(进程间通信)
线程是 CPU 调度和分派的基本单位进程是分配资源的基本单位。
如何理解进程与线程的关系
计算机有很多资源组成比如 CPU、内存、磁盘、鼠标、键盘等就像一个工厂由电力系统、作业车间、仓库、管理办公室和工人组成假定工厂的电力有限一次只能供给一个或少量几个车间使用。也就是说一部分车间开工的时候其他车间都必须停工。背后的含义就是单个CPU 一次只能运行一个任务多个 CPU 能够运行少量任务。线程就好比车间里的工人。一个进程可以包括多个线程他们协同完成某一个任务。
为什么要使用多线程
避免阻塞单个进程只有一个主线程当主线程阻塞的时候整个进程也就阻塞了无法再去做其它的一些功能了。避免CPU空转应用程序经常会涉及到 RPC数据库访问磁盘 IO 等操作这些操作的速度比CPU 慢很多而在等待这些响应时CPU 却不能去处理新的请求导致这种单线程的应用程序性能很差。 cpu 》》 内存 》》 磁盘提升效率一个进程要独立拥有 4GB 的虚拟地址空间而多个线程可以共享同一地址空间线程的切换比进程的切换要快得多。
进程控件与线程空间 二、线程创建函数
_beginthreadex函数用于在 Windows 环境下创建一个新的线程。它的声明通常在 process.h 或 intrin.h 头文件中函数原型为
_ACRTIMP uintptr_t __cdecl _beginthreadex(_In_opt_ void* _Security,_In_ unsigned _StackSize,_In_ _beginthreadex_proc_type _StartAddress,_In_opt_ void* _ArgList,_In_ unsigned _InitFlag,_Out_opt_ unsigned* _ThrdAddr);
_ACRTIMP 是 Microsoft Visual C 中的一个宏用于标识函数或变量的导出属性。在该宏的作用下函数或变量将被声明为在动态链接库DLL中导出以便其他模块可以访问它们。uintptr_t 是 C/C 中的数据类型表示无符号整数类型其大小足以容纳指针的位数。在不同的编译器和平台上其大小可能会有所不同但通常被设计为足够大以容纳指针值。__cdecl 是一种调用约定calling convention它规定了函数参数的传递顺序、堆栈清理责任等规则。在 __cdecl 调用约定中函数参数从右向左入栈由调用者负责清理堆栈空间。
_ACRTIMP uintptr_t __cdecl 表示一个被导出的函数其返回类型为 uintptr_t无符号整数类型采用 cdecl 调用约定。
参数说明如下
security指向 SECURITY_ATTRIBUTES 结构的指针用于指定新线程的安全特性一般可以传入 NULL。stack_size指定新线程的堆栈大小一般可以传入 0 表示使用默认大小。start_address指向线程函数的指针即新线程将要执行的函数。arglist传递给线程函数的参数。initflag控制新线程的初始状态一般可以传入 0。thrdaddr指向一个无符号整数的指针用于接收新线程的标识符。
代码案例
#include stdio.h
#include windows.h
#include process.h//Bingo老师 提了一个需求 打印
//每隔3秒叫焦老师俯卧撑 持续20次
//每隔4秒钟冯老师做一次甩头发 持续30次
//每隔2秒钟叫Bingo老师唱歌 持续50次
//unsigned WINAPI thread_main_hong(void* arg)
{int cnt *((int*)arg);for (int i 0; i cnt; i){printf(fuwocheng);Sleep(3000);}return 0;
}unsigned WINAPI thread_main_ming(void* arg)
{int cnt *((int*)arg);for (int i 0; i cnt; i){printf(shuaitoufa);Sleep(4000);}return 0;
}unsigned WINAPI thread_main_wang(void* arg)
{int cnt *((int*)arg);for (int i 0; i cnt; i){printf(sing);Sleep(2000);}return 0;
}int main()
{int xiaohong 20, xiaoming 30, laowang 50;unsigned int hong_id, ming_id, wang_id;// 三个线程同时启动不分先后顺序谁先给出结果无法确定_beginthreadex(NULL, 0, thread_main_hong, (void*)xiaohong, 0, hong_id);_beginthreadex(NULL, 0, thread_main_ming, (void*)xiaoming, 0, ming_id);_beginthreadex(NULL, 0, thread_main_wang, (void*)laowang, 0, wang_id);// 一旦主线程结束子线程同样结束Sleep(100000);system(pause);return 0;
}
线程的运行顺序不可预测 CreateThread函数是一种微软在 Windows API 中提供了建立新的线程的函数该函数在主线程的基础上创建一个新线程。线程终止运行后线程对象仍然在系统中必须通过CloseHandle 函数来关闭该线程对象。
HANDLE WINAPI CreateThread(_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,_In_ SIZE_T dwStackSize,_In_ LPTHREAD_START_ROUTINE lpStartAddress,_In_opt_ __drv_aliasesMem LPVOID lpParameter,_In_ DWORD dwCreationFlags,_Out_opt_ LPDWORD lpThreadId);
参数说明如下
第一个参数 lpThreadAttributes 表示线程内核对象的安全属性一般传入NULL 表示使用默认设置。第二个参数 dwStackSize 表示线程栈空间大小。传入0 表示使用默认大小1MB。第三个参数 lpStartAddress 表示新线程所执行的线程函数地址多个线程可以使用同一个函数地址。第四个参数 lpParameter 是传给线程函数的参数。第五个参数 dwCreationFlags 指定额外的标志来控制线程的创建为0表示线程创建之后立即就可以进行调度如果为 CREATE_SUSPENDED则表示线程创建后暂停运行这样它就无法调度直到调用ResumeThread()。第六个参数 lpThreadId 将返回线程的 ID 号传入NULL 表示不需要返回该线程 ID 号
需要注意的是在使用完线程句柄后需要通过 CloseHandle 函数关闭线程句柄以释放系统资源。
代码案例
#include stdio.h
#include windows.h
#include process.hDWORD WINAPI ThreadFun(LPVOID p)
{int iMym *((int*)p);printf(我是子线程PID %d,iMym %d\n, GetCurrentThreadId(), iMym);return 0;
}int main()
{printf(main begin\n);HANDLE hThread;DWORD dwThreadID;int m 100;hThread CreateThread(NULL, 0, ThreadFun, m, 0, dwThreadID);printf(我是主线程PID %d\n, GetCurrentThreadId());CloseHandle(hThread);Sleep(2000);system(pause);return 0;
} 三、Windows内核对象与句柄
1. 内核对象Windows 中每个内核对象都只是一个内存块它由操作系统内核分配并只能由操作系统内核进行访问应用程序不能在内存中定位这些数据结构并直接更改其内容。这个内存块是一个数据结构其成员维护着与对象相关的信息。少数成员安全描述符和使用计数是所有内核对象都有的但大多数成员都是不同类型对象特有的。内核对象只是一个内存块这块内存位于操作系统内核的地址空间内存块中存放一个数据结构此数据结构的成员有如安全描述符、使用计数等。
常见的内核对象 : 进程、线程、文件存取符号对象、事件对象、文件对象、作业对象、互斥对象、管道对象、等待计时器对象邮件槽对象信号对象
内核对象为了管理线程/文件等资源而由操作系统创建的数据块。
其创建的所有者肯定是操作系统。
2. 内核对象的使用技术与生命期内核对象的所有者是操作系统内核而非进程。换言之也就是说当进程退出内核对象不一定会销毁。操作系统内核通过内核对象的使用计数知道当前有多少个进程正在使用一个特定的内核对象。初次创建内核对象使用计数为 1。当另一个进程获得该内核对象的访问权之后使用计数加1。如果内核对象的使用计数递减为 0操作系统内核就会销毁该内核对象。也就是说内核对象在当前进程中创建但是当前进程退出时内核对象有可能被另外一个进程访问。这时进程退出只会减少当前进程对引用的所有内核对象的使用计数而不会减少其他进程对内核对象的使用计数即使该内核对象由当前进程创建。那么内核对象的使用计数未递减为 0操作系统内核不会销毁该内核对象。 (1) 进程 1 退出2 不退出时。内核对象 A,B 的引用计数减为 0被操作系统内核销毁而进程1只减少自身对 C,D 的引用计数不会影响进程 2 对 C,D 的引用计数此时 C,D 引用计数不为0不会被销毁。 (2) 进程 2 退出1 不退出时。进程 2 减少自身对 C,D 的引用计数不会影响进程1故A,B,C,D都不会被销毁 (3) 进程 12 均退出时只要 ABCD 不被别的进程使用内核对象 A,B,C,D 的引用计数均递减为0被内核销毁 (4) 进程 1 和 2 均为退出时内核对象 A,B,C,D 的引用计数只要有一个递减为0那么递减为0的内核对象便被内核销毁 3. 操作内核对象Windows 提供了一组函数进行操作内核对象。成功调用一个创建内核对象的函数后会返回一个句柄它表示了所创建的内核对象可由进程中的任何线程使用。在 32 位进程中句柄是一个32 位值在 64 位进程中句柄是一个 64 位值。我们可以使用唯一标识内核对象的句柄调用内核操作函数对内核对象进行操作。
4. 内核对象和其他类型的对象Windows 进程中除了内核对象还有其他类型的对象比如窗口菜单字体等这些属于用户对象和 GDI 对象。要区分内核对象与非内核对象最简单的方式就是查看创建这个对象的函数几乎所有创建内核对象的函数都有一个允许我们指定安全属性的参数。一个对象是不是内核对象通常可以看创建此对象 API 的参数中是否需要PSECURITY_ATTRIBUTES 类型的参数。
5. 每个进程中有一个句柄表handle table这个句柄表仅供内核对象使用如下图 深入理解对内核对象的引用
hThread CreateThread(... , threadId);
当调用了 CreateThread CreateFile 等创建内核对象的函数后就是相当于操作系统多了一个内存块这个内存块就是内核对象也是此时内核对象被创建其数据结构中的引用计数初始为1这样理解只要内核对象被创建其引用计数被初始化为 1这里实则发生两件事创建了一个内核对象和创建线程的函数打开访问了此对象所以内核对象的引用计数加1这时引用计数就为 2 了。
调用 API CreateThread 的时候不仅仅是创建了一个内核对象引用计数1还打开了内核对象1所以引用计数变为 2
当调用 CloseHandle(hThread); 时发生这样的事情系统通过hThread 计算出此句柄在句柄表中的索引然后把那一项处理后标注为空闲可用的项内核对象的引用计数减 1 即此时此内核对象的引用计数为 1之后这个线程句柄与创建时产生的内核对象已经没有任何关系了。不能通过 hThread 句柄去访问内核对象了
只有当内核对象的引用计数为 0 时内核对象才会被销毁而此时它的引用计数为 1那它什么时候会被销毁 当此线程结束的时候它的引用计数再减 1 即为 0内核对象被销毁。此时又有一个新问题产生我们已经关闭了线程句柄也就是这个线程句柄已经和内核对象没有瓜葛了那么那个内核对象是怎么又可以和此线程联系起来了呢其实是创建线程时产生的那个线程 ID代码如下
#include stdio.h
#include windows.h
#include WinBase.hDWORD WINAPI ThreadProc(LPVOID lpParameter)
{printf(I am comming...);while (1) {}return 0;
}int main()
{HANDLE hThread;HANDLE headle2;DWORD threadId;hThread CreateThread(NULL, 0, ThreadProc, NULL, 0, threadId);// CloseHandle(hThread); // 关闭了线程句柄headle2 OpenThread(THREAD_QUERY_INFORMATION, FALSE, threadId);headle2 OpenThread(THREAD_QUERY_INFORMATION, FALSE, threadId);headle2 OpenThread(THREAD_QUERY_INFORMATION, FALSE, threadId);return 0;
} 四、简单的多线程案例
主线程和子线程的结束时间main 函数返回后整个进程终止同时终止其包含的所有线程
#include stdio.h
#include windows.h
#include process.hunsigned WINAPI ThreadFunc(void* arg)
{int i;int cnt *((int*)arg);for (i 0; i cnt; i){Sleep(1000); puts(running thread);}return 0;
}int main(int argc, char* argv[])
{HANDLE hThread;unsigned threadID;int param 5;hThread (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void*)param, 0, threadID);if (hThread 0){puts(_beginthreadex() error);return -1;}Sleep(3000);puts(end of main);return 0;
} 等待一个/多个内核对象变为 signaled 状态
DWORD WINAPI WaitForSingleObject(_In_ HANDLE hHandle, // 指明一个内核对象的句柄_In_ DWORD dwMilliseconds // 等待时间);
这个函数的作用是等待一个指定的内核对象如线程、进程、互斥体、事件等进入 signaled 状态。参数说明如下
hHandle要等待的内核对象的句柄。dwMilliseconds等待的时间以毫秒为单位。如果设置为 INFINITE-1则表示无限等待直到对象进入 signaled 状态或出错。
WaitForSingleObject 函数的返回值表示函数调用的结果可能的取值包括
WAIT_OBJECT_0指定的对象已经进入 signaled 状态。WAIT_TIMEOUT等待超时。WAIT_FAILED函数调用失败。
在操作系统中signaled也称为激发或触发是指内核对象进入一种特殊状态的标识。当内核对象处于 signaled 状态时它表示该对象的某种条件已经满足可以进行相应的操作。
具体来说对于不同类型的内核对象其 signaled 状态的含义可能有所不同
互斥体Mutex表示互斥体当前未被锁定可以被当前线程或其他线程锁定。事件Event表示事件已经触发通常用于线程间的同步和通信。信号量Semaphore表示信号量的计数值大于零可以允许一定数量的线程访问某个资源。定时器Timer表示定时器已经到期可以执行相应的定时操作。进程、线程等表示进程或线程已经终止可以进行后续处理。
通过使用 WaitForSingleObject 函数等待内核对象进入 signaled 状态我们可以实现线程的同步、事件的触发等功能。一旦内核对象进入 signaled 状态WaitForSingleObject 函数将返回并且我们可以根据返回值做出相应的处理。
#include stdio.h
#include windows.h
#include process.hunsigned int __stdcall ThreadFun(LPVOID p)
{int cnt *((int*)p);for (int i 0; i cnt; i){Sleep(1000);puts(running thread);}return 0;
}int main()
{printf(main begin\n);int iParam 5;unsigned int dwThreadID;DWORD wr;HANDLE hThread (HANDLE)_beginthreadex(NULL, 0, ThreadFun, (void*)iParam, 0, dwThreadID);if (hThread NULL){puts(_beginthreadex() error);return -1;}//printf(WaitForSingleObject begin\n);// 阻塞当前线程直到指定的线程通过 hThread 句柄表示结束或终止。if ((wr WaitForSingleObject(hThread, INFINITE)) WAIT_FAILED) // INFINITE 是一个常量表示无限等待时间。{puts(thread wait error);return -1;}printf(WaitForSingleObject end\n);printf(main end\n);system(pause);return 0;
} DWORD WINAPI WaitForMultipleObjects(_In_ DWORD nCount, // 要监测的句柄的组的句柄的个数_In_reads_(nCount) CONST HANDLE* lpHandles, //要监测的句柄的组_In_ BOOL bWaitAll, // TRUE 等待所有的内核对象发出信号 FALSE 任意一个内核对象发出信号_In_ DWORD dwMilliseconds //等待时间
);
每个部分的含义
DWORD这是返回类型表示函数返回一个无符号双字32 位整数作为结果。WINAPI这是函数调用约定的一部分指示函数使用标准的 Windows API 调用约定。_In_ DWORD nCount这是一个输入参数表示要等待的内核对象的数量。_In_reads_(nCount) CONST HANDLE* lpHandles这是一个输入参数表示一个指向 HANDLE 数组的指针这些 HANDLE 表示了要等待的内核对象。_In_ BOOL bWaitAll这是一个输入参数用于指定是等待所有对象还是任意一个对象。如果设为 TRUE则表示只有当所有的内核对象都变为 signaled 状态时函数才会返回如果设为 FALSE则表示只要有任何一个内核对象变为 signaled 状态函数就会返回。_In_ DWORD dwMilliseconds这是一个输入参数指定了等待的时间以毫秒为单位。如果设为 INFINITE则表示无限等待。
测试代码
#include stdio.h
#include windows.h
#include process.h
#define NUM_THREAD 50unsigned WINAPI threadInc(void* arg);
unsigned WINAPI threadDes(void* arg);long long num 0;int main(int argc, char* argv[])
{HANDLE tHandles[NUM_THREAD];int i;printf(sizeof long long: %d \n, sizeof(long long));for (i 0; i NUM_THREAD; i){if (i % 2)tHandles[i] (HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);elsetHandles[i] (HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);}WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);printf(result: %lld \n, num);return 0;
}unsigned WINAPI threadInc(void* arg)
{int i;for (i 0; i 500000; i)num 1;return 0;
}unsigned WINAPI threadDes(void* arg)
{int i;for (i 0; i 500000; i)num - 1;return 0;
}
这个是有问题的 五、线程同步 - 互斥对象
互斥对象(mutex)属于内核对象它能够确保线程拥有对单个资源的互斥访问权。
互斥对象包含一个使用数量一个线程 ID 和一个计数器。其中线程ID 用于标识系统中的哪个线程当前拥有互斥对象计数器用于指明该线程拥有互斥对象的次数。
创建互斥对象调用函数 CreateMutex。调用成功该函数返回所创建的互斥对象的句柄。
请求互斥对象所有权调用函数 WaitForSingleObject 函数。线程必须主动请求共享对象的所有权才能获得所有权。
释放指定互斥对象的所有权调用 ReleaseMutex 函数。线程访问共享资源结束后线程要主动释放对互斥对象的所有权使该对象处于已通知状态。
CreateMutexW 是一个用于创建互斥对象Mutex的 Windows API 函数。让我解释一下每个参数的含义 LPSECURITY_ATTRIBUTES lpMutexAttributes这是一个可选参数用于指定安全描述符控制新创建的互斥对象的安全性。如果不需要特殊的安全设置可以将该参数设为 NULL。 BOOL bInitialOwner这是一个布尔值参数用于指定初始所有权的状态。如果设为 TRUE表示调用线程拥有互斥对象的初始所有权如果设为 FALSE则互斥对象没有初始所有权。 LPCWSTR lpName这是一个可选参数用于指定互斥对象的名称。如果这个参数为 NULL则创建一个匿名的互斥对象如果指定了一个名称系统会尝试使用这个名称来创建或打开一个已存在的同名互斥对象。
CreateMutexW 函数返回一个 HANDLE 类型的值该值表示了新创建的互斥对象的句柄。这个句柄可以用于后续操作和控制互斥对象。
HANDLE WINAPI CreateMutexW(_In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes, //指向安全属性_In_ BOOL bInitialOwner, //初始化互斥对象的所有者 TRUE 立即拥有互斥体_In_opt_ LPCWSTR lpName //指向互斥对象名的指针 L“Bingo”
);
测试代码
#include stdio.h
#include windows.h
#include process.h#define NUM_THREAD 50unsigned WINAPI threadInc(void* arg);
unsigned WINAPI threadDes(void* arg);long long num 0;HANDLE hMutex;int main(int argc, char* argv[])
{HANDLE tHandles[NUM_THREAD];int i;hMutex CreateMutex(NULL, FALSE, NULL);for (i 0; i NUM_THREAD; i){if (i % 2)tHandles[i] (HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);elsetHandles[i] (HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);}WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);CloseHandle(hMutex);printf(result: %lld \n, num);return 0;
}unsigned WINAPI threadInc(void* arg)
{int i;// 这些线程需要抢到 hMutex 才能执行WaitForSingleObject(hMutex, INFINITE);for (i 0; i 500000; i)num 1;ReleaseMutex(hMutex);return 0;
}unsigned WINAPI threadDes(void* arg)
{int i;// 这些线程需要抢到 hMutex 才能执行WaitForSingleObject(hMutex, INFINITE);for (i 0; i 500000; i)num - 1;ReleaseMutex(hMutex);return 0;
}
WaitForSingleObject(hMutex, INFINITE); 等待一个互斥对象的状态变化。
WaitForSingleObject 是一个 Windows API 函数用于等待一个内核对象的状态变化直到对象进入 signaled 状态。hMutex 是一个互斥对象的句柄表示要等待的对象。INFINITE 表示等待时间这里设为 INFINITE表示无限等待直到互斥对象的状态发生变化。
在这行代码中程序将会一直停在这里直到 hMutex 表示的互斥对象进入了 signaled 状态或者在发生错误时才会继续执行后面的代码。这种操作通常用于确保对共享资源的互斥访问比如在多线程编程中控制对临界区的访问。 六、多线程实现群聊的服务端和客户端
服务端使用数组维护通讯的线程增加删除操作有可能触及多线程操作同一资源就有可能造成问题需要加锁限制。
#include WinSock2.h
#include iostream
#include windows.h
#include process.h#pragma comment(lib, ws2_32.lib)#define MAX_CLNT 256
#define MAX_BUF_SIZE 256SOCKET clntSocks[MAX_CLNT];HANDLE hMutex;int clntCnt 0; //当前连接的数目void SendMsg(char* szMsg, int iLen)
{int i 0;WaitForSingleObject(hMutex, INFINITE);for (i 0; i clntCnt; i){send(clntSocks[i], szMsg, iLen, 0);}ReleaseMutex(hMutex);
}//处理客户端连接的函数
unsigned WINAPI HandleCln(void* arg)
{//1 接收传递过来的参数SOCKET hClntSock *((SOCKET*)arg);int iLen 0, i;char szMsg[MAX_BUF_SIZE] { 0 };//2 进行数据的收发 循环接收//接收到客户端的数据// while ((iLen recv(hClntSock, szMsg, sizeof(szMsg),0)) ! 0)// { //收到的数据立马发给所有的客户端// SendMsg(szMsg, iLen);// }while (1){iLen recv(hClntSock, szMsg, sizeof(szMsg), 0);if (iLen ! -1){//收到的数据立马发给所有的客户端SendMsg(szMsg, iLen);}else{break;}}printf(此时连接数目为 %d\n, clntCnt);//3 某个连接断开需要处理断开的连接 遍历WaitForSingleObject(hMutex, INFINITE);for (i 0; i clntCnt; i){if (hClntSock clntSocks[i]){//移位while (i clntCnt){clntSocks[i] clntSocks[i 1];}break;}}clntCnt--; //当前连接数的一个自减printf(断开此时连接数目 %d, clntCnt);ReleaseMutex(hMutex);closesocket(hClntSock);return 0;
}int main()
{// 加载套接字库WORD wVersionRequested;WSADATA wsaData;int err;HANDLE hThread;wVersionRequested MAKEWORD(1, 1);// 初始化套接字库err WSAStartup(wVersionRequested, wsaData);if (err ! 0){return err;}if (LOBYTE(wsaData.wVersion) ! 1 || HIBYTE(wsaData.wVersion) ! 1){WSACleanup();return -1;}//创建一个互斥对象hMutex CreateMutex(NULL, FALSE, NULL);// 新建套接字SOCKET sockSrv socket(AF_INET, SOCK_STREAM, 0);SOCKADDR_IN addrSrv;addrSrv.sin_addr.S_un.S_addr htonl(INADDR_ANY);addrSrv.sin_family AF_INET;addrSrv.sin_port htons(9190);// 绑定套接字到本地IP地址端口号9190if (bind(sockSrv, (SOCKADDR*)addrSrv, sizeof(SOCKADDR)) SOCKET_ERROR){printf(bind ERRORnum %d\n, GetLastError());return -1;}// 开始监听if (listen(sockSrv, 5) SOCKET_ERROR){printf(listen ERRORnum %d\n, GetLastError());return -1;}printf(start listen\n);SOCKADDR_IN addrCli;int len sizeof(SOCKADDR);while (1){// 接收客户连接 sockConn此时来的客户端连接SOCKET sockConn accept(sockSrv, (SOCKADDR*)addrCli, len);//每来一个连接服务端起一个线程安排一个工人维护客户端的连接//每来一个连接全局数组应该加一个成员最大连接数加1WaitForSingleObject(hMutex, INFINITE);clntSocks[clntCnt] sockConn;ReleaseMutex(hMutex);hThread (HANDLE)_beginthreadex(NULL, 0, HandleCln, (void*)sockConn, 0, NULL);printf(Connect client IP: %s \n, inet_ntoa(addrCli.sin_addr));printf(Connect client num: %d \n, clntCnt);}closesocket(sockSrv);WSACleanup();return 0;
}
客户端收发消息需要启多线程
#include WinSock2.h
#include iostream
#include windows.h
#include process.h#pragma comment(lib, ws2_32.lib)
#define NAME_SIZE 32
#define BUF_SIZE 256char szName[NAME_SIZE] [DEFAULT];
char szMsg[BUF_SIZE];//发送消息给服务端
unsigned WINAPI SendMsg(void* arg)
{//1 接收传递过来的参数SOCKET hClntSock *((SOCKET*)arg);char szNameMsg[NAME_SIZE BUF_SIZE]; //又有名字又有消息//循环接收来自于控制台的消息while (1){fgets(szMsg, BUF_SIZE, stdin); //阻塞在这一句//退出机制 当收到q或Q 退出if (!strcmp(szMsg, Q\n) || !strcmp(szMsg, q\n)){closesocket(hClntSock);exit(0);}sprintf(szNameMsg, %s %s, szName, szMsg);//字符串拼接send(hClntSock, szNameMsg, strlen(szNameMsg), 0);//发送}return 0;
}
//接收服务端的消息
unsigned WINAPI RecvMsg(void* arg)
{//1 接收传递过来的参数SOCKET hClntSock *((SOCKET*)arg);char szNameMsg[NAME_SIZE BUF_SIZE]; //又有名字又有消息int iLen 0;while (1){//recv阻塞iLen recv(hClntSock, szNameMsg, NAME_SIZE BUF_SIZE - 1, 0);//服务端断开if (iLen -1){return -1;}// szNameMsg的0到iLen -1 都是收到的数据 iLen个szNameMsg[iLen] 0;//接收到的数据输出到控制台fputs(szNameMsg, stdout);}return 0;
}
// 带参数的main函数用命令行启动 在当前目录按下shift 鼠标右键 cmd
int main(int argc, char* argv[])
{// 加载套接字库WORD wVersionRequested;WSADATA wsaData;int err;SOCKET hSock;SOCKADDR_IN servAdr;HANDLE hSendThread, hRecvThread;wVersionRequested MAKEWORD(1, 1);// 初始化套接字库err WSAStartup(wVersionRequested, wsaData);if (err ! 0){return err;}if (LOBYTE(wsaData.wVersion) ! 1 || HIBYTE(wsaData.wVersion) ! 1){WSACleanup();return -1;}sprintf(szName, [%s], argv[1]);//1 建立sockethSock socket(PF_INET, SOCK_STREAM, 0);// 2 配置端口和地址memset(servAdr, 0, sizeof(servAdr));servAdr.sin_addr.S_un.S_addr inet_addr(127.0.0.1);servAdr.sin_family AF_INET;servAdr.sin_port htons(9190);// 3 连接服务器if (connect(hSock, (SOCKADDR*)servAdr, sizeof(servAdr)) SOCKET_ERROR){printf(connect error error code %d\n, GetLastError());return -1;}// 4 发送服务端的消息 安排一个工人 起一个线程发送消息hSendThread (HANDLE)_beginthreadex(NULL, 0, SendMsg,(void*)hSock, 0, NULL);// 5 接收消息给服务端 安排一个工人 起一个线程接收消息hRecvThread (HANDLE)_beginthreadex(NULL, 0, RecvMsg,(void*)hSock, 0, NULL);//等待内核对象的信号发生变化WaitForSingleObject(hSendThread, INFINITE);WaitForSingleObject(hRecvThread, INFINITE);// 6 关闭套接字closesocket(hSock);WSACleanup();return 0;
}
是否要用多线程有没有并发的必要同时处理不是一条线
是否线程同步是否有可能多个线程操作同一变量的风险 七、线程同步 - 事件对象
线程同步是指多个线程之间协调执行的机制以确保它们按照一定的顺序和时间间隔执行。事件对象是一种可用于实现线程同步的机制之一。
事件对象是一个操作系统提供的同步原语它可以有两种状态signaled有信号和nonsignaled无信号。线程可以等待一个事件对象进入 signaled 状态或者通过设置事件对象为 signaled 状态来通知其他线程。
在使用事件对象进行线程同步时通常会有以下步骤
创建事件对象使用 CreateEvent 函数创建一个事件对象并指定初始状态signaled 或 nonsignaled。等待事件对象线程可以使用 WaitForSingleObject 或类似的函数等待事件对象进入 signaled 状态。如果事件对象当前处于 nonsignaled 状态线程将被阻塞直到事件对象进入 signaled 状态。设置事件对象某个线程可以使用 SetEvent 函数将事件对象设置为 signaled 状态从而唤醒正在等待该事件的线程。重置事件对象使用 ResetEvent 函数将事件对象重置为 nonsignaled 状态以便重新等待事件。
事件对象有两种类型人工重置的事件对象和自动重置的事件对象。这两种事件对象的区别在于当人工重置的事件对象得到通知时等待该事件对象的所有线程均变为可调度线程而当一个自动重置的事件对象得到通知时等待该事件对象的线程中只有一个线程变为可调度线程。
CreateEventW 是一个 Windows API 函数用于创建事件对象。下面是各个参数的含义 LPSECURITY_ATTRIBUTES lpEventAttributes这是一个可选参数用于指定安全描述符控制新创建的事件对象的安全性。如果不需要特殊的安全设置可以将该参数设为 NULL。 BOOL bManualReset这是一个布尔值参数用于指定事件对象的重置方式。如果设为 TRUE表示事件对象是手动重置的如果设为 FALSE表示事件对象是自动重置的。 BOOL bInitialState这是一个布尔值参数用于指定事件对象的初始状态。如果设为 TRUE表示事件对象的初始状态为 signaled如果设为 FALSE表示事件对象的初始状态为非 signaled。 LPCWSTR lpName这是一个可选参数用于指定事件对象的名称。如果这个参数为 NULL则创建一个匿名的事件对象如果指定了一个名称系统会尝试使用这个名称来创建或打开一个已存在的同名事件对象。
CreateEventW 函数返回一个 HANDLE 类型的值该值表示了新创建的事件对象的句柄。这个句柄可以用于后续操作和控制事件对象。
事件对象通常用于线程间的同步和通信可以通过设置和重置事件来实现线程的等待和唤醒操作。
HANDLE WINAPI CreateEventW(_In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,_In_ BOOL bManualReset,_In_ BOOL bInitialState,_In_opt_ LPCWSTR lpName); SetEvent 是一个 Windows API 函数用于将指定的事件对象设置为 signaled 状态。下面是参数的含义
HANDLE hEvent这是要设置为 signaled 状态的事件对象的句柄。
SetEvent 函数将指定的事件对象的状态设置为 signaled从而唤醒正在等待该事件的线程。一旦事件对象被设置为 signaled所有当前正在等待该事件的线程将会继续执行。这是线程间同步和通信的重要手段之一。
BOOL WINAPI SetEvent(_In_ HANDLE hEvent);
每次触发后必有一个或多个处于等待状态下的线程变成可调度状态。 ResetEvent 是一个 Windows API 函数用于将指定的事件对象重置为非 signaled 状态。下面是参数的含义
HANDLE hEvent这是要重置为非 signaled 状态的事件对象的句柄。
ResetEvent 函数将指定的事件对象的状态重置为非 signaled从而使得正在等待该事件的线程进入等待状态。一旦事件对象被重置为非 signaled后续调用 WaitForSingleObject 或类似函数的线程将会被阻塞。
通过使用 SetEvent 和 ResetEvent 函数结合可以实现线程间的同步和通信以及控制线程的执行顺序。
BOOL WINAPI ResetEvent(_In_ HANDLE hEvent); 测试代码
#include stdio.h
#include windows.h
#include process.h#define STR_LEN 100unsigned WINAPI NumberOfA(void* arg);
unsigned WINAPI NumberOfOthers(void* arg);static char str[STR_LEN];static HANDLE hEvent;int main(int argc, char* argv[])
{HANDLE hThread1, hThread2;fputs(Input string: , stdout);fgets(str, STR_LEN, stdin);hEvent CreateEvent(NULL, TRUE, FALSE, NULL); // 手动设置初始为非signaledhThread1 (HANDLE)_beginthreadex(NULL, 0, NumberOfA, NULL, 0, NULL);hThread2 (HANDLE)_beginthreadex(NULL, 0, NumberOfOthers, NULL, 0, NULL);WaitForSingleObject(hThread1, INFINITE);WaitForSingleObject(hThread2, INFINITE);//直到2个线程执行完之后再把事件设置为无信号状态ResetEvent(hEvent);CloseHandle(hEvent);system(pause);return 0;
}unsigned WINAPI NumberOfA(void* arg)
{int i, cnt 0;//再没有执行fputs(Input string: , stdout);//fgets(str, STR_LEN, stdin);SetEvent(hEvent);之前卡在//WaitForSingleObjectWaitForSingleObject(hEvent, INFINITE);for (i 0; str[i] ! 0; i){if (str[i] A)cnt;}printf(Num of A: %d \n, cnt);return 0;
}unsigned WINAPI NumberOfOthers(void* arg)
{int i, cnt 0;//再没有执行fputs(Input string: , stdout);//fgets(str, STR_LEN, stdin);SetEvent(hEvent);之前卡在//WaitForSingleObject// WaitForSingleObject(hEvent, INFINITE);for (i 0; str[i] ! 0; i){if (str[i] ! A)cnt;}printf(Num of others: %d \n, cnt - 1);//把事件对象设置为有信号状态SetEvent(hEvent);return 0;
}
事件对象模拟售票利用通知的方式对共同资源的互斥控制 #include windows.h
#include iostream using namespace std;DWORD WINAPI FunProc1(LPVOID lpParameter);
DWORD WINAPI FunProc2(LPVOID lpParameter);
int ticket 100;
HANDLE g_hEvent;void main()
{HANDLE hThread1;HANDLE hThread2;g_hEvent CreateEvent(NULL, FALSE, TRUE, NULL); //创建事件对象 //SetEvent(g_hEvent); //设置为有信号放弃钥匙 hThread1 CreateThread(NULL, 0, FunProc1, NULL, 0, NULL);hThread2 CreateThread(NULL, 0, FunProc2, NULL, 0, NULL);CloseHandle(hThread1);CloseHandle(hThread2);Sleep(1000); // 让主线程睡眠1秒 CloseHandle(g_hEvent);
}DWORD WINAPI FunProc1(LPVOID lpParameter)
{while (TRUE){WaitForSingleObject(g_hEvent, INFINITE); //申请事件对象 //ResetEvent(g_hEvent); //重置为无信号申请钥匙得带钥匙 if (ticket 0){Sleep(1);cout ticket 1: ticket-- endl;SetEvent(g_hEvent); //设置为有信号放弃钥匙不再拥有 }else{SetEvent(g_hEvent); //设置为有信号 break;}}return 0;
}DWORD WINAPI FunProc2(LPVOID lpParameter)
{while (TRUE){WaitForSingleObject(g_hEvent, INFINITE);//ResetEvent(g_hEvent); if (ticket 0){Sleep(1);cout ticket 2: ticket-- endl;SetEvent(g_hEvent); //设置为有信号 }else{SetEvent(g_hEvent); //设置为有信号 break;}}return 0;
}
执行顺序开始的时候事件对象具有信号当第一个线程申请获得事件对象后进入if语句线程1会暂停1毫秒于是第二根线程运行因为此时g_hEvent已经无信号故无法申请并执行下面的程序此时第一个线程睡醒开始执行自己的任务然后设置对象为有信号可以被其他线程申请于是第二个线程申请得到事件对象................与此往复直到退出循环。
意该程序实现的不是的同步控制但是他可以实现同步见下面
下面这种事件通知方式就是严格的同步方式先让线程1执行再.....两个事件对象进行同步控制
#include windows.h
#include iostream using namespace std; DWORD WINAPI FunProc1(LPVOID lpParameter);
DWORD WINAPI FunProc2(LPVOID lpParameter);
int ticket 100;
HANDLE g_hEvent1,g_hEvent2; void main()
{ HANDLE hThread1; HANDLE hThread2; g_hEvent1 CreateEvent(NULL, FALSE, TRUE, NULL); //创建事件对象1,自动重置事件,已触发 g_hEvent2 CreateEvent(NULL, FALSE, FALSE, NULL); //创建事件对象2,自动重置事件,没触发 //SetEvent(g_hEvent); //设置为有信号放弃钥匙 hThread1 CreateThread(NULL, 0, FunProc1, NULL, 0, NULL); hThread2 CreateThread(NULL, 0, FunProc2, NULL, 0, NULL); CloseHandle(hThread1); CloseHandle(hThread2); Sleep(1000); // 让主线程睡眠1秒 CloseHandle(g_hEvent1); CloseHandle(g_hEvent2); getchar();
}
DWORD WINAPI FunProc1(LPVOID lpParameter)
{ while (TRUE) { WaitForSingleObject(g_hEvent1, INFINITE); //申请有信号的事件对象 //ResetEvent(g_hEvent); //重置为无信号申请钥匙得带钥匙 if (ticket0) { Sleep(1); cout ticket 1: ticket-- endl; SetEvent(g_hEvent2); //设置为有信号放弃钥匙不再拥有 } else { SetEvent(g_hEvent2); //设置为有信号让线程2执行 break; } } return 0;
}
DWORD WINAPI FunProc2(LPVOID lpParameter)
{ while (TRUE) { WaitForSingleObject(g_hEvent2, INFINITE); //ResetEvent(g_hEvent); if (ticket0) { Sleep(1); cout ticket 2: ticket-- endl; SetEvent(g_hEvent1); //让线程1执行 } else { SetEvent(g_hEvent1); break; } } return 0;
} 八、事件对象 与 互斥对象区别
在线程同步中事件对象和互斥对象是两种不同的同步机制它们在实现线程之间协调和互斥访问共享资源时起着不同的作用。下面是它们的区别 事件对象Event 事件对象通常用于线程之间的通信和协调允许一个或多个线程等待某个事件的发生。事件对象通常有两种状态有信号和无信号。当事件处于有信号状态时等待该事件的线程将被唤醒当事件处于无信号状态时等待该事件的线程将被阻塞。事件对象可以通过Set方法将其状态设置为有信号从而唤醒等待该事件的线程也可以通过Reset方法将其状态设置为无信号。典型的应用场景包括线程间的协作、通知和等待特定事件的发生。 互斥对象Mutex 互斥对象用于控制多个线程对共享资源的访问确保在同一时刻只有一个线程可以访问共享资源从而避免数据竞争和不一致性。当一个线程获得了互斥对象的锁时其他线程将被阻塞直到持有锁的线程释放锁。互斥对象通常由锁定、解锁等操作来实现对临界区的保护防止多个线程同时访问导致的数据错误。
因此事件对象和互斥对象在线程同步中有不同的作用事件对象用于线程之间的通信和协作而互斥对象用于保护共享资源确保线程安全访问。在实际的多线程编程中开发人员需要根据具体的需求选择合适的同步机制来实现线程之间的协调和资源访问的保护。 九、线程同步 - 信号量
内核对象的状态
触发状态(有信号状态)表示有可用资源。未触发状态无信号状态表示没有可用资源
工作原理以一个停车场是运作为例。假设停车场只有三个车位一开始三个车位都是空的。这时如果同时来了五辆车看门人允许其中三辆不受阻碍的进入然后放下车拦剩下的车则必须在入口等待此后来的车也都不得不在入口处等待。这时有一辆车离开停车场看门人得知后打开车拦放入一辆如果又离开两辆则又可以放入两辆如此往复。这个停车系统中每辆车就好比一个线程看门人就好比一个信号量看门人限制了可以活动的线程。假如里面依然是三个车位但是看门人改变了规则要求每次只能停两辆车那么一开始进入两辆车后面得等到有车离开才能有车进入但是得保证最多停两辆车。对于 Semaphore 而言就如同一个看门人限制了可活动的线程数。
信号量的组成
计数器该内核对象被使用的次数最大资源数量标识信号量可以控制的最大资源数量带符号的32位当前资源数量标识当前可用资源的数量带符号的32 位。即表示当前开放资源的个数注意不是剩下资源的个数只有开放的资源才能被线程所申请。但这些开放的资源不一定被线程占用完。比如当前开放5 个资源而只有3 个线程申请则还有 2 个资源可被申请但如果这时总共是7 个线程要使用信号量显然开放的资源 5 个是不够的。这时还可以再开放2 个直到达到最大资源数量。
信号量的规则如下
如果当前资源计数大于 0那么信号量处于触发状态(有信号状态)表示有可用资源。如果当前资源计数等于 0那么信号量属于未触发状态无信号状态表示没有可用资源。系统绝对不会让当前资源计数变为负数当前资源计数绝对不会大于最大资源计数 信号量与互斥量不同的地方是它允许多个线程在同一时刻访问同一资源但是需要限制在同一时刻访问此资源的最大线程数目。信号量对象对线程的同步方式与前面几种方法不同信号允许多个线程同时使用共享资源。
创建信号量
HANDLE WINAPI CreateSemaphoreW(_In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // Null 安全属性_In_ LONG lInitialCount, //初始化时共有多少个资源是可以用的。 0未触发状//态无信号状态表示没有可用资源_In_ LONG lMaximumCount, //能够处理的最大的资源数量 3_In_opt_ LPCWSTR lpName //NULL 信号量的名称);
增加信号量
WINAPI ReleaseSemaphore(_In_ HANDLE hSemaphore, //信号量的句柄_In_ LONG lReleaseCount, //将lReleaseCount值加到信号量的当前资源计数上面0- 1_Out_opt_ LPLONG lpPreviousCount //当前资源计数的原始值);
关闭句柄
CloseHandle(_In_ _Post_ptr_invalid_ HANDLE hObject
);
代码示例
#include stdio.h
#include windows.h
#include process.hunsigned WINAPI Read(void* arg);
unsigned WINAPI Accu(void* arg);static HANDLE semOne;
static HANDLE semTwo;
static int num;int main(int argc, char* argv[])
{HANDLE hThread1, hThread2;semOne CreateSemaphore(NULL, 0, 1, NULL);//semOne 没有可用资源 只能表示0或者1的二进制信号量 无信号semTwo CreateSemaphore(NULL, 1, 1, NULL);//semTwo 有可用资源有信号状态 有信号hThread1 (HANDLE)_beginthreadex(NULL, 0, Read, NULL, 0, NULL);hThread2 (HANDLE)_beginthreadex(NULL, 0, Accu, NULL, 0, NULL);WaitForSingleObject(hThread1, INFINITE);WaitForSingleObject(hThread2, INFINITE);CloseHandle(semOne);CloseHandle(semTwo);system(pause);return 0;
}unsigned WINAPI Read(void* arg)
{int i;for (i 0; i 5; i){fputs(Input num: , stdout); // 1 5 11printf(begin read\n); // 3 6 12//等待内核对象semTwo的信号如果有信号继续执行如果没有信号等待WaitForSingleObject(semTwo, INFINITE);printf(beginning read\n); //4 10 16scanf(%d, num);// 信号量semTwo的计数将增加1。ReleaseSemaphore(semOne, 1, NULL);}return 0;
}
unsigned WINAPI Accu(void* arg)
{int sum 0, i;for (i 0; i 5; i){printf(begin Accu\n); //2 9 15//等待内核对象semOne的信号如果有信号继续执行如果没有信号等待WaitForSingleObject(semOne, INFINITE);printf(beginning Accu\n); //7 13sum num;printf(sum %d \n, sum); // 8 14// 信号量semTwo的计数将增加1。ReleaseSemaphore(semTwo, 1, NULL);}printf(Result: %d \n, sum);return 0;
} 十、线程同步 - 关键代码段
关键代码段也称为临界区工作在用户方式下。它是指一个小代码段在代码能够执行前它必须独占对某些资源的访问权。通常把多线程中访问同一种资源的那部分代码当做关键代码段。
1. 初始化关键代码段调用 InitializeCriticalSection 函数初始化一个关键代码段。
该函数只有一个指向CRITICAL_SECTION结构体的指针。在调用InitializeCriticalSection函数之前首先需要构造一个CRITICAL_SCTION结构体类型的对象然后将该对象的地址传递给InitializeCriticalSection函数。
InitializeCriticalSection(_Out_ LPCRITICAL_SECTION lpCriticalSection
);
2. 进入关键代码段
调用EnterCriticalSection函数以获得指定的临界区对象的所有权该函数等待指定的临界区对象的所有权如果该所有权赋予了调用线程则该函数就返回否则该函数会一直等待从而导致线程等待。
VOID WINAPI EnterCriticalSection(_Inout_ LPCRITICAL_SECTION lpCriticalSection
);
3. 退出关键代码段
线程使用完临界区所保护的资源之后需要调用LeaveCriticalSection函数释放指定的临界区对象的所有权。之后其他想要获得该临界区对象所有权的线程就可以获得该所有权从而进入关键代码段访问保护的资源。
VOID WINAPI LeaveCriticalSection(_Inout_ LPCRITICAL_SECTION lpCriticalSection
);
4. 删除临界区
当临界区不再需要时可以调用DeleteCriticalSection函数释放该对象该函数将释放一个没有被任何线程所拥有的临界区对象的所有资源。
WINBASEAPI VOID WINAPI DeleteCriticalSection(_Inout_ LPCRITICAL_SECTION lpCriticalSection
);
代码示例
#include stdio.h
#include windows.h
#include process.h int iTickets 5000;
CRITICAL_SECTION g_cs;// A窗口 B窗口DWORD WINAPI SellTicketA(void* lpParam)
{while (1){EnterCriticalSection(g_cs);//进入临界区if (iTickets 0){Sleep(1);iTickets--;printf(A remain %d\n, iTickets);LeaveCriticalSection(g_cs);//离开临界区}else{LeaveCriticalSection(g_cs);//离开临界区break;}}return 0;
}DWORD WINAPI SellTicketB(void* lpParam)
{while (1){EnterCriticalSection(g_cs);//进入临界区if (iTickets 0){Sleep(1);iTickets--;printf(B remain %d\n, iTickets);LeaveCriticalSection(g_cs);//离开临界区}else{LeaveCriticalSection(g_cs);//离开临界区break;}}return 0;
}int main()
{HANDLE hThreadA, hThreadB;hThreadA CreateThread(NULL, 0, SellTicketA, NULL, 0, NULL); //2hThreadB CreateThread(NULL, 0, SellTicketB, NULL, 0, NULL); //2CloseHandle(hThreadA); //1CloseHandle(hThreadB); //1InitializeCriticalSection(g_cs); //初始化关键代码段Sleep(40000);DeleteCriticalSection(g_cs);//删除临界区system(pause);return 0;
} 十一、线程死锁
死锁是指多个线程因竞争资源而造成的一种僵局互相等待若无外力作用这些进程都将无法向前推进。
#include stdio.h
#include windows.h
#include process.h
int iTickets 5000;
CRITICAL_SECTION g_csA;
CRITICAL_SECTION g_csB;
// A窗口 B窗口
DWORD WINAPI SellTicketA(void* lpParam)
{while (1){EnterCriticalSection(g_csA);//进入临界区ASleep(1);EnterCriticalSection(g_csB);//进入临界区Bif (iTickets 0){Sleep(1);iTickets--;printf(A remain %d\n, iTickets);LeaveCriticalSection(g_csB);//离开临界区BLeaveCriticalSection(g_csA);//离开临界区A}else{LeaveCriticalSection(g_csB);//离开临界区BLeaveCriticalSection(g_csA);//离开临界区Abreak;}}return 0;
}
DWORD WINAPI SellTicketB(void* lpParam)
{while (1){EnterCriticalSection(g_csB);//进入临界区BSleep(1);EnterCriticalSection(g_csA);//进入临界区Aif (iTickets 0){Sleep(1);iTickets--;printf(B remain %d\n, iTickets);LeaveCriticalSection(g_csA);//离开临界区ALeaveCriticalSection(g_csB);//离开临界区B}else{LeaveCriticalSection(g_csA);//离开临界区ALeaveCriticalSection(g_csB);//离开临界区Bbreak;}}return 0;
}
int main()
{HANDLE hThreadA, hThreadB;hThreadA CreateThread(NULL, 0, SellTicketA, NULL, 0, NULL); //2hThreadB CreateThread(NULL, 0, SellTicketB, NULL, 0, NULL); //2CloseHandle(hThreadA); //1CloseHandle(hThreadB); //1InitializeCriticalSection(g_csA); //初始化关键代码段AInitializeCriticalSection(g_csB); //初始化关键代码段BSleep(40000);DeleteCriticalSection(g_csA);//删除临界区DeleteCriticalSection(g_csB);//删除临界区system(pause);return 0;
} 十二、四种线程同步方式总结
Windows线程同步的方式主要有四种互斥对象Mutex事件对象event关键代码段criticalSection信号量。
对于上面介绍的三种线程同步的方式它们之间的区别如下所述
用户级别的关键代码段只能本进程中内核级别的互斥量/事件/信号量可以跨进程
互斥对象和事件以及信号量都属于内核对象利用内核对象进行线程同步时速度较慢但利用互斥对象和事件对象这样的内核对象可以在多个进程中的各个线程间进行同步。关键代码段工作在用户方式下同步速度较快但在使用关键代码段时很容易进入死锁状态因为在等待进入关键代码段时无法设定超时值。
通常在编写多线程程序并需要实现线程同步时首选关键代码段由于它的使用比较简单如果是在MFC程序中使用的话可以在类的构造函数Init中调用InitializeCriticalSection函数在该类的析构函数中调用DeleteCriticalSection函数在所需保护的代码前面调用EnterCriticalSection函数在访问完所需保护的资源后调用LeaveCriticalSection函数。可见关键代码段在使用上是非常方便的但有几点需要注意
在程序中调用了EnterCriticalSection后要相应的调用LeaveCriticalSection函数否则其他等待该临界区对象所有权的线程将无法执行。如果访问关键代码段时使用了多个临界区对象就要注意防止线程死锁的发生。另外如果需要在多个进程间的各个线程间实现同步的话可以使用互斥对象和事件对象或者信号量。 完结撒花
文章转载自: http://www.morning.cfqyx.cn.gov.cn.cfqyx.cn http://www.morning.c7491.cn.gov.cn.c7491.cn http://www.morning.ffksr.cn.gov.cn.ffksr.cn http://www.morning.jkmjm.cn.gov.cn.jkmjm.cn http://www.morning.hrpmt.cn.gov.cn.hrpmt.cn http://www.morning.xrwsg.cn.gov.cn.xrwsg.cn http://www.morning.ncwgt.cn.gov.cn.ncwgt.cn http://www.morning.pclgj.cn.gov.cn.pclgj.cn http://www.morning.gqfjb.cn.gov.cn.gqfjb.cn http://www.morning.flhnd.cn.gov.cn.flhnd.cn http://www.morning.nwjd.cn.gov.cn.nwjd.cn http://www.morning.saastob.com.gov.cn.saastob.com http://www.morning.ybgt.cn.gov.cn.ybgt.cn http://www.morning.wncb.cn.gov.cn.wncb.cn http://www.morning.nsyzm.cn.gov.cn.nsyzm.cn http://www.morning.nfqyk.cn.gov.cn.nfqyk.cn http://www.morning.kjrlp.cn.gov.cn.kjrlp.cn http://www.morning.jxhlx.cn.gov.cn.jxhlx.cn http://www.morning.tzpqc.cn.gov.cn.tzpqc.cn http://www.morning.bfgbz.cn.gov.cn.bfgbz.cn http://www.morning.lkcqz.cn.gov.cn.lkcqz.cn http://www.morning.kwqt.cn.gov.cn.kwqt.cn http://www.morning.yrqb.cn.gov.cn.yrqb.cn http://www.morning.hhxwr.cn.gov.cn.hhxwr.cn http://www.morning.kbbmj.cn.gov.cn.kbbmj.cn http://www.morning.jgnjl.cn.gov.cn.jgnjl.cn http://www.morning.prgnp.cn.gov.cn.prgnp.cn http://www.morning.yqjjn.cn.gov.cn.yqjjn.cn http://www.morning.ffydh.cn.gov.cn.ffydh.cn http://www.morning.mpyry.cn.gov.cn.mpyry.cn http://www.morning.xczyj.cn.gov.cn.xczyj.cn http://www.morning.zydr.cn.gov.cn.zydr.cn http://www.morning.ndxrm.cn.gov.cn.ndxrm.cn http://www.morning.fhrt.cn.gov.cn.fhrt.cn http://www.morning.zlces.com.gov.cn.zlces.com http://www.morning.tmzlt.cn.gov.cn.tmzlt.cn http://www.morning.qftzk.cn.gov.cn.qftzk.cn http://www.morning.rqknq.cn.gov.cn.rqknq.cn http://www.morning.fwwkr.cn.gov.cn.fwwkr.cn http://www.morning.qszyd.cn.gov.cn.qszyd.cn http://www.morning.sxtdh.com.gov.cn.sxtdh.com http://www.morning.wqbfd.cn.gov.cn.wqbfd.cn http://www.morning.mpwgs.cn.gov.cn.mpwgs.cn http://www.morning.htfnz.cn.gov.cn.htfnz.cn http://www.morning.burpgr.cn.gov.cn.burpgr.cn http://www.morning.frzdt.cn.gov.cn.frzdt.cn http://www.morning.pkdng.cn.gov.cn.pkdng.cn http://www.morning.xzrbd.cn.gov.cn.xzrbd.cn http://www.morning.dpruuode.cn.gov.cn.dpruuode.cn http://www.morning.drrt.cn.gov.cn.drrt.cn http://www.morning.smwlr.cn.gov.cn.smwlr.cn http://www.morning.xxrwp.cn.gov.cn.xxrwp.cn http://www.morning.ywpwg.cn.gov.cn.ywpwg.cn http://www.morning.cttgj.cn.gov.cn.cttgj.cn http://www.morning.trnl.cn.gov.cn.trnl.cn http://www.morning.ttryd.cn.gov.cn.ttryd.cn http://www.morning.wnhgb.cn.gov.cn.wnhgb.cn http://www.morning.qbrs.cn.gov.cn.qbrs.cn http://www.morning.zwzlf.cn.gov.cn.zwzlf.cn http://www.morning.rkxk.cn.gov.cn.rkxk.cn http://www.morning.hqmfn.cn.gov.cn.hqmfn.cn http://www.morning.rpkl.cn.gov.cn.rpkl.cn http://www.morning.qnbgk.cn.gov.cn.qnbgk.cn http://www.morning.nqmwk.cn.gov.cn.nqmwk.cn http://www.morning.qgjp.cn.gov.cn.qgjp.cn http://www.morning.kjdxh.cn.gov.cn.kjdxh.cn http://www.morning.pgrsf.cn.gov.cn.pgrsf.cn http://www.morning.rwmft.cn.gov.cn.rwmft.cn http://www.morning.wfyzs.cn.gov.cn.wfyzs.cn http://www.morning.mfsxd.cn.gov.cn.mfsxd.cn http://www.morning.lyhry.cn.gov.cn.lyhry.cn http://www.morning.qlxgc.cn.gov.cn.qlxgc.cn http://www.morning.qblcm.cn.gov.cn.qblcm.cn http://www.morning.ccyns.cn.gov.cn.ccyns.cn http://www.morning.zqzhd.cn.gov.cn.zqzhd.cn http://www.morning.ysgnb.cn.gov.cn.ysgnb.cn http://www.morning.gmgnp.cn.gov.cn.gmgnp.cn http://www.morning.mlycx.cn.gov.cn.mlycx.cn http://www.morning.fmkjx.cn.gov.cn.fmkjx.cn http://www.morning.rlbg.cn.gov.cn.rlbg.cn