黑客24小时在线接单网站

黑客在线接单,网站入侵,渗透测试,渗透网站,入侵网站

*** 安全编程:多线程编程基础知识

线程是过程中的执行单位(每个过程必须有一个主线程),一个过程可以有多个线程,而一个线程只存在于一个过程中。在数据关系中,过程和线程是一对多的关系。线程没有系统资源,线程使用的所有资源都从过程申请到系统,线程有CPU的时间片。

同一过程中的不同线程在单处理器(或单核处理器)上交替获得CPU时间片。不同的线程可以同时在多处理器(或多核处理器)上运行CPU这可以提高程序运行的效率。此外,必须在某些方面使用多线程。例如,当扫描磁盘并在程序界面上同步显示当前扫描位置时,必须使用多线程。因为程序界面显示和磁盘扫描工作在同一线程中,界面不断重新显示,这将导致软件看起来卡住。在这种情况下,可以通过分为两个线程来解决这个问题。界面显示由主线程完成,扫描磁盘由另一个线程完成,两个线程协同工作,实现当前扫描状态的实时显示。

首先,了解线程的创建。线程的创建和使用CreateThread()函数的原型如下:

  • HANDLECreateThread(
  • LPSECURITY_ATTRIBUTESlpThreadAttributes,//SD
  • DWORDdwStackSize,//initialstacksize
  • LPTHREAD_START_ROUTINElpStartAddress,//threadfunction
  • LPVOIDlpParameter,//threadargument
  • DWORDdwCreationFlags,//creationoption
  • LPDWORDlpThreadId//threadidentifier
  • );
  • 参数说明如下。

    lpThreadAttributes:指明创建线程的安全属性,为指向 SECURITY_ATTRIBUTES 结构指针一般设置为 NULL。

    dwStackSize:如果指定线程为 ,则使用缺失的堆栈尺寸NULL,与工艺主线程栈相同。

    lpStartAddress:对于指定的线程函数,线程从函数的入口开始运行。当函数返回时,意味着线程终止运行回调函数。线程函数的定义如下:

  • DWORDWINAPIThreadProc(
  • LPVOIDlpParameter//threaddata
  • );
  • 线程函数的返回值为DWORD类型,线程函数只有一个参数CreateThread()函数中给出。函数的函数名称可以随意给定。很多时候,它不能保证执行CreateThread()函数后线程将立即启动,线程需要等待启动CPU的调度,CPU将时间片给该线程时,该线程才会执行,当然这个时间短到可以忽略它。

    lpParameter:该参数表示传递给线程函数的参数,可以指向任何数据类型的指针。这是一个通过结构体等一次性将多个参数传输到线程函数的指针。

    dwCreationFlags:该参数指示了创建线程后的线程状态。创建线程后,线程可以立即执行(这里的立即执行意味着它不会被人为地等待),或者线程可以暂停。如果需要立即执行,参数设置为 0;如果要暂停线程,则该参数设置为 CREATE_SUSPENDED,需要线程执行时调用ResumeThread()函数将线程状态调整为等待运行状态,然后由 CPU 分配时间片后执行。

    lpThreadId:该参数用于返回新线程 ID。

    如果线程创建成功,函数返回线程的句柄,否则返回NULL。创建新线程后,线程开始执行。但如果是在dwCreationFlags中使用了CREATE_SUSPENDED参数,然后线程不会立即执行,而是先挂起,直到调用ResumeThread然后开始线程。线程的句柄需要通过CloseHandle()关闭以释放资源。

    代码如下:

  • #include<windows.h>
  • #include<stdio.h>
  • DWORDWINAPIThreadProc(LPVOIDlpParam)
  • {
  • printf("ThreadProc\r\n");
  • return0;
  • }
  • intmain()
  • {
  • HANDLEhThread=CreateThread(NULL,0,ThreadProc,NULL,0,NULL);
  • printf("main\r\n");
  • CloseHandle(hThread);
  • return0;
  • }
  • 在主线程中打印代码“main”,在创建的新线程中打印一行“ThreadProc”。编译操作,查看其操作结果,如图1所示。

    图1 多线程程序输出结果

    从图1可以看出,程序输出与预期结果不同。程序有什么问题?每个线程都有自己的CPU时间片,当主线程创建新的线程时,主线程CPU时间片还没有结束,它会继续向下执行。由于主线程代码很少,主线程在CPU在分配的时间片中完成并退出。因为主线程的结束意味着过程结束并退出。因此,虽然代码中创建的线程是创建的,但根本没有机会执行。那么,在如此短的代码中,如何确保新创建的线程在主线程结束前执行呢?换句话说,主线程的运行需要等待新线程的完成。它需要在这里使用WaitForSingleObject()函数的原型如下:

  • DWORDWaitForSingleObject(
  • HANDLEhHandle,//handletoobject
  • DWORDdwMilliseconds//time-outinterval
  • );
  • 参数说明如下。

    hHandle:该参数是要等待的对象句柄。

    dwMilliseconds:如果设置为 0,则立即返回,如果设置为 INFINITE,这意味着一直在等待线程函数的返回。INFINITE 是系统定义的宏,其定义如下。

  • #defineINFINITE0xFFFFFFFF
  • 如果函数失败,则返回WAIT_FAILED;如果等待对象编程处于激发状态,则返回WAIT_ OBJECT_0;在等待对象变成 *** 状态之前,等待时间结束将返回WAIT_TIMEOUT。

    修改上述代码,在CreateThread()函数后添加以下代码:

  • WaitForSingleObject(hThread,INFINITE);
  • 添加WaitForSingleObject()函数后,主线程将等待新创建的线程结束继续执行主线程的后续代码。控制台上的输出如图2所示。

    图2 主线程等待子线程的执行

    WaitForSingleObject()只能等一个线程,但在程序中往往需要创建多个线程来执行,所以如果需要等待几个线程的完成,WaitForSingleObject()函数无能为力。然而,除了提供系统外,系统还提供WaitForSingleObject()除函数外,还提供了另一个函数,可以等待多个线程的完成状态WaitForMultipleObjects()函数的定义如下:

  • DWORDWaitForMultipleObjects(
  • DWORDnCount,//numberofhandlesinarray
  • CONSTHANDLE*lpHandles,//object-handlearray
  • BOOLfWaitAll,//waitoption
  • DWORDdwMilliseconds//time-outinterval
  • );
  • 该函数的参数比WaitForSingleObject()函数多2个参数,下面介绍这些参数。

    nCount:该参数用于指示要等待函数的线程数量。该参数的值范围为 1 至 MAXIMUM_WAIT _OBJECTS 之间。

    lpHandles:该参数是指向等待线程句柄的数组指针。

    fWaitAll:如果设置为 TRUE,等待一切。

    dwMilliseconds:该参数与 WaitForSingleObject()函数中的 dwMilliseconds 用法相同。

    WaitForSingleObject()和WaitForMultipleObjects()除了等待线程外,两个函数还可以等待多线程同步和相互排斥的核心对象。

    在使用多线程时,有许多问题需要考虑和注意。例如,多线程同时操作共享资源,需要按照一定的顺序执行。看一个简单的多线程例子:

  • intg_Num_One=0;
  • DWORDWINAPIThreadProc(LPVOIDlpParam)
  • {
  • intnTmp=0;
  • for(inti=0;i<10;i )
  • {
  • nTmp=g_Num_One;
  • nTmp ;
  • //Sleep(1)作用是让出CPU
  • //
  • Sleep(1);
  • g_Num_One=nTmp;
  • }
  • return0;
  • }
  • 每个线程都有一个CPU时间片,当自己的时间片运行完成后,CPU该线程将停止并切换到其他线程。当多线程同时运行共享资源时,这种切换会带来无形的问题。这里的代码相对较短,在一个CPU时间片肯定会完成,不能反映线程切换造成的错误。为了实现线程切换引起的错误,添加到代码中Sleep(1)主动让出线程CPU,让CPU切换线程。在代码中,线程处理的共享资源是全球变量g_Num_One变量。主函数创建线程的代码如下:

  • intmain()
  • {
  • HANDLEhThread[10]={0};
  • inti;
  • for(i=0;i<10;i )
  • {
  • hThread[i]=CreateThread(NULL,0,ThreadProc,NULL,0,NULL);
  • }
  • WaitForMultipleObjects(10,hThread,TRUE,INFINITE);
  • for(i=0;i<10;i )
  • {
  • CloseHandle(hThread[i]);
  • }
  • printf("g_Num_One=%d\r\n",g_Num_One);
  • return0;
  • }
  • 通过主函数CreateThread()创建了10个线程,每个线程都被允许g_Num_One自增10次,每次增1次。然后10个线程会使g_Num_One结果变成100。编译上述代码,查看输出结果,如图3所示。

    图3 多线程操作共享资源的错误结果

    这个结果与预测结果不同。为什么会有这样的差异?这里有一个模拟分析。为了便于分析,将线程的数量减少到两个线程,即A线程和B线程。

    ① g_Num_One的初始值为0。

    ② 当A线程中执行nTmp = g_Num_One和nTmp 后(此时nTmp因为Sleep(1)线程切换发生在这个时候g_Num_One初始值仍为0。

    ③ 当B线程中执行nTmp = g_Num_One和nTmp 后(此时nTmp因为Sleep(1)线程切换的原因再次发生。

    ④ A线程执行g_Num_One = nTmp,此时g_Num_One值为1,然后在下一个循环中执行nTmp = g_Num_One和nTmp 操作,再切换。

    ⑤ B线程执行g_Num_One = nTmp,此时g_Num_One的值为1。

    到第⑤步时,不继续往下分析,已经可以看出原因了。g_Num_One值是最后一次nTmp赋值后的值(线程中的局部变量属于线程中的私有变量,虽然是相同的线程函数,但是nTmp每一行都是私有的)。

    临界区用于解决这个问题。临界区的对象是一个CRITICAL_SECTION数据结构,Windows操作系统使用数据结构来保护关键代码,以确保多线程下的共享资源。Windows只允许一个线程进入临界区。

    临界区有四个函数,即初始化临界区的对象(InitializeCriticalSection(),进入临界区(EnterCriticalSection(),离开临界区(LeaveCriticalSection()(DeleteCriticalSection()。临界区很好地保护了共享资源,在现实生活中也有许多类似的例子。例如,在体检过程中,体检室只有一名体检医生,体检医生会要求一名患者进行体检,其他人不能进入,当患者离开时,下一名患者可以进入。这里的体检医生是一个共享的资源,每个体检患者都有多个不同的线程。临界区以类似的方式保护共享资源不受损害。以下是对临界区域函数的定义,如下:

  • VOIDInitializeCriticalSection(
  • LPCRITICAL_SECTIONlpCriticalSection//criticalsection
  • );
  • VOIDEnterCriticalSection(
  • LPCRITICAL_SECTIONlpCriticalSection//criticalsection
  • );
  • VOIDLeaveCriticalSection(
  • LPCRITICAL_SECTIONlpCriticalSection//criticalsection
  • );
  • VOIDDeleteCriticalSection(
  • LPCRITICAL_SECTIONlpCriticalSection//criticalsection
  • );
  • 这4个API指向函数的参数CRITICAL_SECTION结构体指针。修改上述有问题的代码,修改后的代码如下:

  • #include<windows.h>
  • #include<stdio.h>
  • intg_Num_One=0;
  • CRITICAL_SECTIONg_cs;
  • DWORDWINAPIThreadProc(LPVOIDlpParam)
  • {
  • intnTmp=0;
  • for(inti=0;i<10;i )
  • {
  • //进入临界区
  • EnterCriticalSection(&g_cs);
  • nTmp=g_Num_One;
  • nTmp ;
  • Sleep(1);
  • g_Num_One=nTmp;
  • //离开临界区
  • LeaveCriticalSection(&g_cs);
  • }
  • return0;
  • }
  • intmain()
  • {
  • InitializeCriticalSection(&g_cs);
  • HANDLEhThread[10]={0};
  • inti;
  • for(i=0;i<10;i )
  • {
  • hThread[i]=CreateThread(NULL,0,ThreadProc,NULL,0,NULL);
  • }
  • WaitForMultipleObjects(10,hThread,TRUE,INFINITE);
  • printf("g_Num_One=%d\r\n",g_Num_One);
  • for(i=0;i<10;i )
  • {
  • CloseHandle(hThread[i]);
  • }
  • DeleteCriticalSection(&g_cs);
  • return0;
  • }
  • 编译上述代码并运行,输出结果是正确的结果,即g_Num_One值为100。除了使用临界区域外,还有其他 *** 可以同步和相互排斥线程。在开发多线程序时,应注意多线程的同步和相互排斥。临界区域的对象只能用于多线程的相互排斥。

       
    • 评论列表:
    •  礼忱娇痞
       发布于 2022-06-06 03:02:59  回复该评论
    • 到可以忽略它。lpParameter:该参数表示传递给线程函数的参数,可以指向任何数据类型的指针。这是一个通过结构体等一次性将多个参数传输到线程函数的指针。dwCreationFlags:该参数指示了创建线程
    •  闹旅城鱼
       发布于 2022-06-06 05:52:06  回复该评论
    • );for(i=0;i<10;i ){CloseHandle(hThread[i]);}printf("g_Num_One=%d\r\n",g_Num_One);return0;}通过主函数CreateThread()创建了10个线程,每个线程都被允许

    发表评论:

    Powered By

    Copyright Your WebSite.Some Rights Reserved.