参考MoreWindows 的 秒杀多线程面试题系列
一,如果在代码中有使用标准C运行库中的函数时,尽量使用_beginthreadex()来代替CreateThread()。_beginthreadex()函数在创建新线程时会分配并初始化一个_tiddata块。这个_tiddata块自然是用来存放一些需要线程独享的数据。事实上新线程运行时会首先将_tiddata块与自己进一步关联起来。然后新线程调用标准C运行库函数如strtok()时就会先取得_tiddata块的地址再将需要保护的数据存入_tiddata块中。这样每个线程就只会访问和修改自己的数据而不会去篡改其它线程的数据了。
#include <Windows.h>
#include <process.h>
#include <iostream>
using namespace std;
int g_nCount;
unsigned int WINAPI ThreadFun(LPVOID pM)
{
g_nCount++;
cout << "线程ID号的子进程: " << GetCurrentThreadId() << "报数为" << g_nCount << endl;
return 0;
}
int main()
{
cout << "子线程报数" << endl;
const int THREAD_NUM = 10;
HANDLE handle[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i++)
handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
//WaitForSingleObject(handle, INFINITE);
getchar();
return 0;
}
多线程不加限制的结果。
二,原子操作。Interlocked系列函数。
在多线程环境中对一个变量进行读写时,我们需要有一种方法能够保证对一个值的递增操作是原子操作——即不可打断性,一个线程在执行原子操作时,其它线程必须等待它完成之后才能开始执行该原子操作。Windows系统为我们提供了一些以Interlocked开头的函数来完成这一任务(下文将这些函数称为Interlocked系列函数)。
下面列出一些常用的Interlocked系列函数:
1.增减操作
LONG __cdecl InterlockedIncrement(LONG volatile* Addend);
LONG __cdecl InterlockedDecrement(LONG volatile* Addend);
返回变量执行增减操作之后的值。
LONG __cdecl InterlockedExchangeAdd(LONG volatile* Addend, LONG Value);
返回运算后的值,注意!加个负数就是减。
2.赋值操作
LONG __cdecl InterlockedExchange(LONG volatile* Target, LONG Value);
Value就是新值,函数会返回原先的值。
#include <Windows.h>
#include <process.h>
#include <iostream>
using namespace std;
volatile long g_nLoginCount;
const int THREAD_NUM = 65;//WaitForMultipleObjects()函数第一个参数的限制是64,所以使用大于64个线程也会造成错误。
DWORD WINAPI ThreadFun(void *pPM)
{
Sleep(100);//some work should to do
//g_nLoginCount++;
InterlockedIncrement((LPLONG)&g_nLoginCount);
Sleep(50);
return 0;
}
int main()
{
cout << "原子操作 Interlocked系列函数的使用" << endl;
int num = 20;
while (num--)
{
g_nLoginCount = 0;
HANDLE handle[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i++)
{
handle[i] = CreateThread(NULL, 0, ThreadFun, NULL, 0, NULL);
//handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
}
WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
cout << "有" << THREAD_NUM << "用户登录后的记录结果是" << g_nLoginCount << endl;
}
//WaitForSingleObject(handle, INFINITE);
getchar();
return 0;
}
三,临界区(实现线程间互斥)
1.临界区共初始化化、销毁、进入和离开临界区域四个函数。
2.临界段可以解决线程的互斥问题,但因为具有“线程所有权”,所以无法解决同步问题。
3.推荐关键段与自旋锁配合使用。
注:自旋锁应用在SMP上,在获取不到临界资源时不选择立即进行睡眠而选择循环等待。在SMP中,由于对资源的锁定时间较短,多等待完全可能获得资源,可以减少上下文切换的开销。
四,事件(实现线程间同步)
五,互斥量(实现进程间互斥)
互斥量也是一个内核对象,它用来确保一个线程独占一个资源的访问。互斥量与关键段的行为非常相似,并且互斥量可以用于不同进程中的线程互斥访问资源。使用互斥量Mutex主要将用到四个函数。
CreateMutex,OpenMutex,ReleaseMutex,CloseHandle()。
六,信号量(实现进程间同步)
信号量Semaphore常用有三个函数,CreateSemaphore,OpenSemaphore,ReleaseSemaphore
七,生产者消费者问题
八,读者写者问题