《秒杀多线程》的笔记

根据博主《秒杀多线程》的笔记进一步一点点记录阅读时不懂或者模糊的点。

链接: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 那么唤醒一个执行线程;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值