根据博主《秒杀多线程》的笔记进一步一点点记录阅读时不懂或者模糊的点。
链接:https://blog.csdn.net/MoreWindows/article/details/7392749
实现多线程demo
因为使用vs2008的原因,无法支持C++11,所以不能使用库文件。
讨论: 句柄是什么?
这个知识点在《windows编程》中。从语义通俗理解是提东西(大)的手柄(小)。因为windows内核对象(创建的线程、打开的文件、窗口等)拥有大量的属性,不可能每次都传递这么多字节,所以在进程的地址空间中设一张表,表里头专门保存一些编号和由这个编号对应一个地址,而由那个地址去引用实际的对象,这个我编号就是句柄。
相等于指向指针的指针。
关于WaitForSingleObject函数
其中,内核对象是指Windows中进程对象,线程对象等统称为内核对象,它是地址空间的一个内存块。
可以理解为,我们需要等待某一线程完成了才能继续做事情,例如一个文件和两个线程对其进行访问(规定只能一个线程访问),此时用到了操作系统学到的信号量,信号量为false可以访问,线程1访问,信号量更新为true,所以线程2只能等待,等待的过程就是这个函数。
WaitForSingleObject(线程2函数,时间)
-
关于__stdcall
__stdcall用来修饰函数,被该关键字修饰的函数,其参数都是从右向左依次被压入到栈中,函数调用在返回前需要清理堆栈,被调函数在返回前负责清理堆栈。
https://www.xuebuyuan.com/2098171.html
-
C/C++ 中的 volatile 关键字和 const 对应,用来修饰变量
-
WaitForMultipleObjects函数
可以等待Windows所有的内核对象
注意:WaitForMultipleObjects函数,最大等待数为64
DWORD WaitForMultipleObjects(
DWORD nCount, // 列表中的句柄数量
CONST HANDLE *lpHandles, // 句柄数组的指针
BOOL fWaitAll, //bWaitAll 等待的类型,如果为TRUE,表示除非对象都发出信号,否则就一直等待下去;如果FALSE,表示任何对象发出信号即可
DWORD dwMilliseconds // 指定要等候的毫秒数。如设为零,表示立即返回。如指定常数INFINITE,则可根据实际情况无限等待下去
);
讨论: 关于执行g_nLoginCount++时线程修改值,根据评论区更正原因:线程执行的并发性,若修改成这样,会出现报数顺序混乱。
InterlockedIncrement和 printf这两句之间是可能出现线程切换的,有可能线程a进行完InterlockedIncrement后,被线程b抢先执行printf语句。
InterlockedIncrement只能保证g_nCount在每个线程里面都加1,但是显示的顺序有可能是乱的。
讨论: 主线程和子线程到底是什么?
在此之前,回忆下线程的意义:线程是程序进程的执行,而进程是线程的执行环境。
这里主线程函数是main函数,在main函数内创建的线程属于子线程。
优先级:子线程初始优先级与父线程相同。不过主线程先启动占用了cpu资源,如果存在主线程和子线程争抢cpu执行权的话,看运气,谁抢到就让谁执行。也可以设置优先级。
讨论: void* 指针应该怎么获取值呢?
void *pm;
int n = *(int*)pm;
string str = *(string*)pm;
讨论: sleep(0)的含义
该线程放弃当前分得时间片的剩余执行时间,从“进行态”直接进入“就绪态”,把剩余时间片让给其他线程使用。
线程id是整个系统唯一的,线程编号有时会一样,就是因为同一个线程可以重复进入临界区。
讨论:多个enter()可以用一个leave()就结束了?
不可以,关键段记录线程id好和线程占用关键段的次数RecursionCount,
关键段CRITICAL_SECTION
它的作用:也就是临界区,资料显示CRITICAL_SECTION是锁定了资源,但在原理上,CRITICAL_SECTION是不能够“锁定”资源的,它能够完成的功能,是同步不同线程的代码段。
简单说,当一个线程执行了EnterCritialSection之后,cs里面的信息便被修改,以指明哪一个线程占用了它。而此时,并没有任何资源被“锁定”。不管什么资源,其它线程都还是可以访问的(当然,执行的结果可能是错误的)。只不过,在这个线程尚未执行LeaveCriticalSection之前,其它线程碰到EnterCritialSection语句的话,就会处于等待状态,相当于线程被挂起了。 这种情况下,就起到了保护共享资源的作用。
讨论:“关键段是有“线程所有权”概念的”
“线程所有权”可以简单理解为一张房卡,OwningThread字段记录房卡,EnterCriticalSection()会更新RecursionCount字段记录拿着房卡进入房间的次数,所以其他线程调用EnterCriticalSection()是在等待(房间只有一张房卡),当拥有线程所有权的线程(拥有房卡的)调用LeaveCriticalSection()时(交出房卡)。
关键段可以解决互斥问题,无法解决同步问题。
互斥可以理解为是一种特殊的同步,无次序。
-
旋转锁的概念
旋转锁是一种非阻塞锁,由某个线程独占。采用旋转锁时,等待线程并不静态地阻塞在同步点,而是必须“旋转”,不断尝试直到最终获得该锁。
讨论: 旋转锁为何能提高关键段的性能
函数会立即把调用线程切换到等待状态。这意味着线程必须从用户模式切换到内核模式??
在多处理器机器上,等待线程完全切换到内核模式之前,占用资源释放了的话,会浪费大量cpu时间。
所以在关键段中,当调用EnterCriticalSection的时候,它会用一个旋转锁不断的循环,尝试在一段时间内获得对象的资源的访问权。只有当尝试失败的时候,线程才会切换到内核模式并进入等待状态。
提高性能就在于轮询时如果占用资源释放了,就不用切换到等待状态了。
类似于:CPU访问内存时先去访问cache,如未命中再去访问内存。
讨论: 在评论区提到其他线程也可以调用LeaveCriticalSection,使当前线程拥有者的进入的次数减一
即表示未拥有临界区的线程在尝试执行离开临界区操作时,当所有者推出理解趋势,他会被永久破坏。任何线程在随后的调用Enter时都将被永 远地阻塞
事件Event
PulseEvent()函数相当于
SetEvent();//将事件触发,等待线程变为可调度状态
ResetEvent();//将事件设为未触发
自动置位,则对该事件调用WaitForSingleObject()后会自动调用ResetEvent()使事件变成未触发状态。
PulseEvent函数:
1.对于手动置位事件,所有正处于等待状态下线程都变成可调度状态。
2.对于自动置位事件,所有正处于等待状态下线程只有一个变成可调度状态。
讨论: SetEvent放在子线程函数中?
因为主线程和子线程并发执行,当子线程SetEvent将事件触发,主线程的WaitForSingleObject继续下去,执行下一个子线程。
修改文章例子,让线程编号顺序输出
sleep(50)让先执行的子线程等了一会儿,这样会让线程次序混乱。
互斥量Mutex
互斥量有线程拥有权,因为会记录线程ID
CreateMutex();第二个参数
TRUE表示互斥量对象内部会记录创建它的线程的线程ID号并将递归计数设置为1,由于该线程ID非零,所以互斥量处于未触发状态。如果传入FALSE,那么互斥量对象内部的线程ID号将设置为NULL,递归计数设置为0,
wait相当于关键段的EnterCriticalSection申请房卡,ReleaseMutex 相当于归还房卡,
讨论: 第一个例子有误,根据评论区进行修正
错误点,无法证明主线程和子线程无法同步
创建的互斥量本来就是触发状态,因为互斥量无线程用,主线程wait时,主线程挂起,但互斥量是触发立即返回继续下去。
ReleaseMutex前必须使用等待函数,等待互斥量触发,所有权归这个线程才实现了ReleaseMutex触发互斥量的效果
const int THREAD_NUM = 10;
//关键段变量声明
CRITICAL_SECTION g_csThreadCode;
//声明互斥量
HANDLE g_hThreadParameter;
unsigned int __stdcall Fun(void *pPM)
{
int nThreadNum = *(int *)pPM;
WaitForSingleObject(g_hThreadParameter,INFINITE);//等待互斥量触发
ReleaseMutex(g_hThreadParameter);//触发互斥量
Sleep(50);//some work should to do
EnterCriticalSection(&g_csThreadCode);//进入各子线程互斥区域
g_nNum++;
Sleep(0);//some work should to do
printf("线程编号为%d 全局资源值为%d\n", nThreadNum, g_nNum);
LeaveCriticalSection(&g_csThreadCode);//离开各子线程互斥区域
return 0;
}
//主函数,所谓主函数其实就是主线程执行的函数。
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
g_hThreadParameter = CreateMutex(NULL, FALSE, NULL);//初始状态为触发
//关键段初始化
InitializeCriticalSection(&g_csThreadCode);
HANDLE handle[THREAD_NUM];
g_nNum = 0;
int i = 0;
for (int i = 0; i < THREAD_NUM; i++)
{
handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);//0
}
WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);//等待所有子线程返回
DeleteCriticalSection(&g_csThreadCode);
CloseHandle(g_hThreadParameter);//释放互斥量
for (i = 0; i < THREAD_NUM; i++)
CloseHandle(handle[i]);
system("pause");
return 0;
}
信号量 Semaphore
信号量是一个整数 count,提供两个原子(atom,不可分割)操作:P 操作和 V 操作,或是说 wait 和 signal 操作。
P操作 (wait操作):count 减1;如果 count < 0 那么挂起执行线程;
V操作 (signal操作):count 加1;如果 count <= 0 那么唤醒一个执行线程;