想用线程来输出从1到20.结果不对,请提示下。该如何处理

想用线程来输出从1到20.结果不对,请提示下。。
#include   <stdio.h>
#include   <windows.h>

CRITICAL_SECTION   gcs;

DWORD   threadA(void   *lp);

int   main()
{
        DWORD   Thread_id[3];
        HANDLE   hThread[3];
       
        InitializeCriticalSection(&gcs);         //创建临界区
        //创建线程
        hThread[0]   =   CreateThread(NULL,   0,   (LPTHREAD_START_ROUTINE)threadA,   (void   *)1,   0,   NULL);
        hThread[1]   =   CreateThread(NULL,   0,   (LPTHREAD_START_ROUTINE)threadA,   (void   *)2,   0,   NULL);
        hThread[2]   =   CreateThread(NULL,   0,   (LPTHREAD_START_ROUTINE)threadA,   (void   *)3,   0,   NULL);
        //等待所有线程结束
        WaitForMultipleObjects(3,   hThread,   TRUE,   5000);
        //删除临界区
        DeleteCriticalSection(&gcs);                
        return   0;
}

DWORD   threadA(void   *lp)
{
        int   id   =   (int)lp;
        int   i   =   1;


        while(i   <   20)
        {
                EnterCriticalSection(&gcs);
                printf( "thread   %d   i   =   %d\n ",   id,   i++);
                LeaveCriticalSection(&gcs);
        }
        return   0;
}

这里输出的有30几行,为什么i不是从1到20,而是乱的。




------解决方案--------------------
首先,回调函数应当是__stdcall的,所以应当是DWORD WINAPI threadA(void *lp)
其次,你用到了CRT函数,因此应该用_beginthreadex来创建线程
------解决方案--------------------
_beginthread/_endthread

这个函数究竟做了什么呢?它的代码在thread.c中。阅读代码,可以看到它最终也是通过CreateThread来创建线程的,主要区别在于,它先分配了一个_tiddata,并且调用了_initptd来初始化这个分配了的指针。而这个指针最后会被传递到CRT的线程包装函数_threadstart中,在那里会把这个指针作为一个TLS(Thread Local Storage)保存起来。然后_threadstart会调用我们传入的线程函数,并且在那个函数退出后调用_endthread。这里也可以看到,_threadstart用一个__try/__except块把我们的函数包了起来,并且在发生异常的时候,调用exit退出。(_threadstart和endthread的代码都在thread.c中)
这个_tiddata是一个什么样的结构呢?它在mtdll.h中定义,它的成员被很多CRT函数所用到,譬如int _terrno,这是这个线程中的错误标志;char* _token,strtok以来这个变量记录跨函数调用的信息,...。
那么_endthread又做了些什么呢?除了调用浮点的清除代码以外,它还调用了_freeptd来释放和这个线程相关的tiddata。也就是说,在_beginthread里面分配的这块内存,以及在线程运行过程中其它CRT函数中分配并且记录在这个内存结构中的内存,在这里被释放了。
通过上面的代码,我们可以看到,如果我使用_beginthread函数创建了线程,它会为我创建好CRT函数需要的一切,并且最后无需我操心,就可以把清除工作做得很好,可能唯一需要注意的就是,如果需要提前终止线程,最好是调用_endthread或者是返回,而不要调用ExitThread,因为这可能造成内存释放不完全。同时我们也可以看出,如果我们用CreateThread函数创建了线程,并且不对C运行库进行调用(包括任何间接调用),就不必担心什么问题了。

CreateThread和CRT

或许有人会说,我用CreateThread创建线程以后,我也调用了C运行库函数,并且也使用ExitThread退出了,可是我的程序运行得好好的,既没有因为CRT没有初始化而崩溃,也没有因为忘记调用_endthread而发生内存泄漏,这是为什么呢,让我们继续我们的CRT之旅。
假设我用CreateThread创建了一个线程,我调用strtok函数来进行字符串处理,这个函数肯定是需要某些额外的运行时支持的。strtok的源代码在strtok.c中。从代码可见,在多线程情况下,strtok的第一句有效代码就是_ptiddata ptd = _getptd(),它通过这个来获得当前的ptd。可是我们并没有通过_beginthread来创建ptd,那么一定是_getptd捣鬼了。打开tidtable.c,可以看到_getptd的实现,果然,它先尝试获得当前的ptd,如果不能,就重新创建一个,因此,后续的CRT调用就安全了。可是这块ptd最终又是谁释放的呢?打开dllcrt0.c,可以看到一个DllMain函数。在VC中,CRT既可以作为一个动态链接库和主程序链接,也可以作为一个静态库和主程序链接,这个在Project Setting-> Code Generations里面可以选。当CRT作为DLL链接到主程序时,DllMain就是CRT DLL的入口。Windows的DllMain可以由四种原因调用:Process Attach/Process Detach/Thread Attach/Thread Detach,最后一个,也就是当线程函数退出后但是线程还没有销毁前,会在这个线程的上下文中用Thread Detach调用DllMain,这里,CRT做了一个_freeptd(NULL),也就是说,如果有ptd,就free掉。所以说,恰巧没有发生内存泄漏是因为你用的是动态链接的CRT。