精华内容
下载资源
问答
  • 关于操作系统线程,linux操作系统线程控制原语int pthread_create(pthread_t *thread, const ...根据man配置的信息可以得出pthread_create会创建一个线程,这个函数是linux系统的函数,可以C或者C++直接调用...

    关于操作系统的线程,linux操作系统的线程控制原语

    int pthread_create(pthread_t *thread, const pthread_attr_t *attr,

    void *(*start_routine) (void *), void *arg);

    f772f02f7925

    根据man配置的信息可以得出pthread_create会创建一个线程,这个函数是linux系统的函数,可以用C或者C++直接调用,上面信息也告诉程序员这个函数在pthread.h, 这个函数有四个参数

    f772f02f7925

    在linux上启动一个线程的代码:

    #include //头文件

    #include

    pthread_t pid;//定义一个变量,接受创建线程后的线程id

    //定义线程的主体函数

    void* thread_entity(void* arg)

    {

    printf("i am new Thread!");

    }

    //main方法,程序入口,main和java的main一样会产生一个进程,继而产生一个main线程

    int main()

    {

    //调用操作系统的函数创建线程,注意四个参数

    pthread_create(&pid,NULL,thread_entity,NULL);

    //usleep是睡眠的意思,那么这里的睡眠是让谁睡眠呢?

    //为什么需要睡眠?如果不睡眠会出现什么情况

    usleep(100);

    printf("main\n");

    }

    假设有了上面知识的铺垫,那么可以试想一下java的线程模型到底是什么情况呢?

    在java代码里启动一个线程的代码

    public class Example4Start {

    public static void main(String[] args) {

    Thread thread = new Thread(){

    @Override

    public void run() {

    System.out.println("i am new Thread!");

    }

    };

    thread.start();

    }

    }

    这里启动的线程和上面我们通过linux的pthread_create函数启动的线程有什么关系呢?

    只能去可以查看start()的源码了,看看java的start()到底干了什么事才能对比出来.

    start源码的部分截图,可以看到这个方法最核心的就是调用了一个start0方法,而start0方法又是一个native方法,故而如果要搞明白start0我们需要查看Hotspot的源码,好吧那我们就来看一下Hotspot的源码吧,Hotspot的源码怎么看么?一般直接看openjdk的源码,openjdk的源码如何查看、编译调试?openjdk的编译我们后面会讨论,在没有openjdk的情况下,我们做一个大胆的猜测,java级别的线程其实就是操作系统级别的线程,什么意思呢?说白了我们大胆猜想 start-start0-ptherad_create

    我们鉴于这个猜想来模拟实现一下。

    f772f02f7925

    f772f02f7925

    如何来模拟实现呢?

    public class LubanThread {

    public static void main(String[] args) {

    LubanThread lubanThread =new LubanThread();

    lubanThread.start0();

    }

    private native void start0();

    }

    这里我们让自己写的start0调用一个本地方法,在本地方法里面去启动一个系统线程,好吧我们写一个c程序来启动本地线程

    #include

    #include

    pthread_t pid;

    void* thread_entity(void* arg)

    {

    while(1){

    usleep(100);

    printf("I am new Thread\n");

    }

    }

    int main()

    {

    pthread_create(&pid,NULL,thread_entity,NULL);

    while(1){

    usleep(100);

    printf("I am main\n");

    }

    }

    在linux上编译、运行上述C程序

    gcc thread.c -o thread.out -pthread

    ./thread.out

    f772f02f7925

    结果是两个线程一直在交替执行,得到我们预期的结果。现在的问题就是我们如何通过start0调用这个c程序,这里就要用到JNI了,如果你预习了epoll的课(当然epoll我没有讲完,看看大家的接受程度再决定要不要讲完)那么JNI调用就应该懂了

    好吧你要是实在不懂,再说一遍

    java代码如下:

    package com.luban.concurrency;

    public class LubanThread {

    static {

    System.loadLibrary( "LubanThreadNative" );

    }

    public static void main(String[] args) {

    LubanThread lubanThread =new LubanThread();

    lubanThread.start0();

    }

    private native void start0();

    }

    装载库,保证JVM在启动的时候就会装载,故而一般是也给static

    System.loadLibrary( "LubanThreadNative" );

    编译成class文件

    f772f02f7925

    生成.h头文件

    javah packageName.className

    需要注意的运行javah命令得在包外面和编译不一样,编译运行javac得在包当中

    f772f02f7925

    f772f02f7925

    生成的.h文件,最好把他移动到和class文件同级目录吧

    f772f02f7925

    继而开始编写C程序,要上面那个thread.c程序上做一点修改,这里我复制一份出来修改,修改的时候需要参考那个这个.h文件,一下是.h文件的内容

    /* DO NOT EDIT THIS FILE - it is machine generated */

    #include

    /* Header for class com_luban_concurrency_LubanThread */

    #ifndef _Included_com_luban_concurrency_LubanThread

    #define _Included_com_luban_concurrency_LubanThread

    #ifdef __cplusplus

    extern "C" {

    #endif

    /*

    * Class: com_luban_concurrency_LubanThread

    * Method: start0

    * Signature: ()V

    */

    JNIEXPORT void JNICALL Java_com_luban_concurrency_LubanThread_start0

    (JNIEnv *, jobject);

    #ifdef __cplusplus

    }

    #endif

    #endif

    上面第十五代码中的Java_com_luban_concurrency_LubanThread_start0方法就是你需要在C程序中定义的方法。

    首先复制一份thread.c

    f772f02f7925

    修改threadNew.c,定义一个方法Java_com_luban_concurrency_LubanThread_start0,在方法中启动一个子线程,代码如下

    #include

    #include

    #include "com_luban_concurrency_LubanThread.h"//记得导入刚刚编译的那个.h文件

    pthread_t pid;

    void* thread_entity(void* arg)

    {

    while(1){

    usleep(100);

    printf("I am new Thread\n");

    }

    }

    //这个方法要参考.h文件的15行代码,这里的参数得注意,你写死就行,不用明白为什么

    JNIEXPORT void JNICALL Java_com_luban_concurrency_LubanThread_start0

    (JNIEnv *env, jobject c1){

    pthread_create(&pid,NULL,thread_entity,NULL);

    while(1){

    usleep(100);

    printf("I am main\n");

    }

    }

    int main()

    {

    return 0;

    }

    解析类,把这个threadNew.c编译成为一个动态链接库,这样在java代码里会被laod到内存

    libLubanThreadNative这个命名需要注意libxx,xx就等于你java那边写的字符串

    gcc -fPIC -I /usr/lib/jvm/java-1.8.0-openjdk/include -I /usr/lib/jvm/java-1.8.0-openjdk/include/linux -shared -o libLubanThreadNative.so threadNew.c

    做完这一系列事情之后需要把这个.so文件加入到path,这样java才能load到

    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:{libLubanThreadNative.so}所在的路径

    f772f02f7925

    万事俱备,直接测试,运行我们自己写的那个java类直接测试看看结果能不能启动线程

    f772f02f7925

    回车

    f772f02f7925

    牛逼!我们已经通过自己写的一个类,启动了一个线程,但是这个线程函数体是不是java的是C程序的,这个java线程的run方法不同。接下来我们来实现一下这个run

    首先在java的代码里面提供一个run方法

    package com.luban.concurrency;

    public class LubanThread {

    static {

    System.loadLibrary( "LubanThreadNative" );

    }

    public static void main(String[] args) {

    LubanThread lubanThread =new LubanThread();

    lubanThread.start0();

    }

    //这个run方法,要让C程序员调用到,就完美了

    public void run(){

    System.out.println("I am java Thread !!");

    }

    private native void start0();

    }

    所以现在的问题就是让C程序当中的thread_entity方法调用到java当中的run方法?思路同样是jni反调用java方法

    gcc -o threadNew threadNew.c -I /usr/lib/jvm/java-1.8.0-openjdk/include -I /usr/lib/jvm/java-1.8.0-openjdk/include/linux -L /usr/lib/jvm/java-1.8.0-openjdk/jre/lib/amd64/server -ljvm -pthread

    LD_LIBRARY_PATH=/usr/lib/jvm/java-1.8.0-openjdk/jre/lib/amd64/server ./a

    展开全文
  • 来源自自1999年7月MSJ杂志的《Win32 Q&A》栏目你也许会说我一直CreateThread来创建线程,一直都工作得好好的,为什么_beginthreadex来代替CreateThread,下面让我来告诉你为什么。回答一个问题可以有两种方式...

    来源自自1999年7月MSJ杂志的《Win32 Q&A》栏目

    你也许会说我一直用CreateThread来创建线程,一直都工作得好好的,为什么要用_beginthreadex来代替CreateThread,下面让我来告诉你为什么。
    回答一个问题可以有两种方式,一种是简单的,一种是复杂的。
    如果你不愿意看下面的长篇大论,那我可以告诉你简单的答案:_beginthreadex在内部调用了CreateThread,在调用之前_beginthreadex做了很多的工作,从而使得它比CreateThread更安全。
    OK,下面是复杂的回答,^_^:
    微软在发布VC的同时附带了6个CRT库,下表列出了这些库的名称和详细描述:

    Library Name
    Description
    LIBC.LIB
    Statically linked library for single-threaded applications (this is the default library chosen when you create a new project).
    LIBCD.LIB
    Statically linked debug version of the library for single-threaded applications.
    LIBCMT.LIB
    Statically linked release version of the library for multithreaded applications.
    LIBCMTD.LIB
    Statically linked debug version of the library for multithreaded applications.
    MSVCRT.LIB
    Import library for dynamically linking the release version of the MSVCRT.DLL library. The library supports both single-threaded and multithreaded applications.
    MSVCRTD.LIB
    Import library for dynamically linking the debug version of the MSVCRT.DLL library. The library supports both single-threaded and multithreaded applications.

    在VC 6和VS 2003中可以在下图中所示的项目进行设置:


    此主题相关图片如下:


    此主题相关图片如下:

    为什么我们需要两个几乎相同的库来分别对待单线程和多线程程序?说起来也很简单,两个字——效率。让我们从头说起,标准CRT库出现于1970年左右,那时,线程的概念尚未出现在任何一个操作系统上。但是,线程毕竟是出现了,那好,让我们来看看下面这个例子,在这个例子中我们使用了CRT的全局变量errno:

    BOOL fFailure = (system("NOTEPAD.EXE README.TXT") == -1);

    if (fFailure) {
         switch (errno) {
         case E2BIG: // Argument list or environment too big
             break;

       case ENOENT: // Command interpreter cannot be found
           break;

       case ENOEXEC: // Command interpreter has bad format
           break;

       case ENOMEM: // Insufficient memory to run command
           break;
       }
    }

    设想这样的情况,当上面的代码执行到system函数之后,if声明之前的时候,操作系统打断了它,而转去执行进程中的另一个线程,而这个线程正好使用了会设置errno的某个CRT函数......于是,问题就出现了。
    为了解决这个问题,每个线程需要自己的errno全局变量,而且还需要一些机制来使得它们使用它们自己的errno变量,而不是其他线程的。当然,errno只是“多线程不服症”的其中一个受害者,其他受害者还有:_doserrno, strtok, _wcstok, strerror, _strerror, tmpnam, tmpfile, asctime, _wasctime, gmtime, _ecvt, _fcvt。
    于是,为了让C和C++程序能够正常工作,必须创建一个数据结构,并把它与每一个线程关连起来,只有这样才能调用CRT库时不至于误入“他线程家园”。
    那么系统怎么知道在创建一个新线程时分配这个数据块呢?回答是系统不知道,这一切责任都在你,只有你才能确保所有的事情正常完成。
    是不是有点重任在肩的感觉?呵呵,不要紧,其你要做的和标题所说的一样,只需要调用_beginthreadex函数即可:

    unsigned long _beginthreadex(void *security,
         unsigned stack_size,
         unsigned (*start_address)(void *), void *arglist,
         unsigned initflag, unsigned *thrdaddr);

    _beginthreadex的参数列表与CreateThread一模一样,只是参数名与类型有少许差异罢了。这是因为Microsoft觉得CRT函数不应该对Windows的数据类型有任何依赖。两者返回
    的东西也是一样的,所以即使你使用了CreateThread函数,要替换成_beginthreadex也是一件很容易的事情。
    因为两者的数据类型不完全一致,所以我们需要作一些转换来避免编译器的抱怨,为了简化这项工作,你可以使用我所写的这个宏:

    typedef unsigned (__stdcall *PTHREAD_START) (void *);

    #define chBEGINTHREADEX(psa, cbStack, pfnStartAddr, /
         pvParam, fdwCreate, pdwThreadID)                /
           ((HANDLE) _beginthreadex(                     /
              (void *) (psa),                            /
              (unsigned) (cbStack),                      /
              (PTHREAD_START) (pfnStartAddr),            /
              (void *) (pvParam),                        /
              (unsigned) (fdwCreate),                    /
              (unsigned *) (pdwThreadID)))

    注意_beginthreadex函数只存在于CRT库的多线程版本中,如果你链接到了一个单线程运行时库,链接器会毫不客气地报告“unresolved external symbol”错误。另外,还需要注意的是VS在创建新项目时默认选择的是单线程库,所以需要记得修改设置。
    说了这么多,只是说了一些概念,至于_beginthreadex为什么要比CreateThread更好,还是需要事实来说话的,当然,程序员所说的事实,就是代码了,代码之前,了无秘密,所以下面让我们来看看CRT库的代码是怎样的。
    首先,自然是主角人物_beginthreadex(你可以在THREADEX.C中找到它),因为没必要在这里重复写出源代码,所以我只给出伪代码版本的_beginthreadex:

    unsigned long __cdecl _beginthreadex (
         void *psa,
         unsigned cbStack,
         unsigned (__stdcall * pfnStartAddr) (void *),
         void * pvParam,
         unsigned fdwCreate,
         unsigned *pdwThreadID) {

         _ptiddata ptd;         // Pointer to thread's data block
         unsigned long thdl;    // Thread's handle

         // Allocate data block for the new thread
         if ((ptd = calloccrt(1, sizeof(struct tiddata))) == NULL)
             goto errorreturn;

         // Initialize the data block
         initptd(ptd);

         // Save the desired thread function and the parameter
         // we want it to get in the data block
         ptd->_initaddr = (void *) pfnStartAddr;
         ptd->_initarg = pvParam;

         // Create the new thread
         thdl = (unsigned long) CreateThread(psa, cbStack,
             _threadstartex, (PVOID) ptd, fdwCreate, pdwThreadID);
         if (thdl == NULL) {
             // Thread couldn't be created, cleanup and return failure
             goto error_return;
         }

         // Create created OK, return the handle
         return(thdl);

    error_return:
         // Error: data block or thread couldn't be created
         _free_crt(ptd);
         return((unsigned long)0L);
    }

    _beginthreadex的代码中有几个地方需要重点注意:
    首先每个线程会从CRT的堆上获得真正属于它自己的tiddata内存块。tiddata数据结构你可以在MTDLL.H中找到。传递给_beginthreadex的线程函数的地址被保存在tiddata内存块中。要传递给该线程函数的参数也被保存在这里。_beginthreadex接下来调用CreateThread,注意,这时CreateThread在新线程中执行的并不是pfnStartAddr函数,而是一个名为_threadstartex的函数。同时,传递给线程函数的参数也不是pvParam,而是tiddata结构的地址。最后,如果一切顺利将返回线程句柄,如果任何一个操作失败,将返回NULL。
    现在,tiddata结构已经被分配并初始化完成,下面来看看该结构是如何关联到线程的。这次的对象是_threadstartex,同样也在THREADEX.C中,同样也给出伪代码:

    static unsigned long WINAPI _threadstartex (void* ptd) {
        // Note: ptd is the address of this thread's tiddata block

        // Associate the tiddata block with this thread
        TlsSetValue(__tlsindex, ptd);

        // Save this thread ID in the tiddata block
        ((_ptiddata) ptd)->_tid = GetCurrentThreadId();

        // Initialize floating-point support (code not shown)

        // Wrap desired thread function in SEH frame to
        // handle runtime errors and signal support
        __try {
            // Call desired thread function passing it the desired parameter
            // Pass threads exit code value to _endthreadex
            _endthreadex(
              ( (unsigned (WINAPI *)(void *))(((_ptiddata)ptd)->_initaddr) )
                  ( ((_ptiddata)ptd)->_initarg ) ) ;
        }
        __except(_XcptFilter(GetExceptionCode(), GetExceptionInformation()){
            // The C-Runtime's exception handler deals with runtime errors
            // and signal support, we should never get it here.
            _exit(GetExceptionCode());
        }

        // We never get here, the thread dies in this function
        return(0L);
    }

    _threadstartex同样也有一些东西需要我们注意。新线程开始时会执行BaseThreadStart(位于Kernel32.DLL中),然后跳到_threadstartex。_threadstartex的唯一参数就是新线程的tiddata内存块地址。TlsSetValue完成了将tiddata结构与线程关联起来的目的(这里的tiddata结构被称为线程本地存储,TLS,顾名思义,就是属于每个线程自己的数据)。
    在事实上的线程函数周围放置了一个结构化异常处理体(A structured exception handling frame)。这个处理体主要负责处理与运行时库有关的很多东西,比如运行时错误(像抛出但却没有被捕获的C++异常这类东西)和CRT的signal函数。这很重要,如果你使用CreateThread创建了线程,然后又调用了CRT的signal函数,那么signal函数将无法正常工作。
    注意,这时还不能返回到BaseThreadStart,如果这样做,线程会死掉,退出码会正常设置,但tiddata内存块不会被销毁,这就会造成内存泄漏。为了防止泄漏,需要调用_endthreadex,并且将退出码传递给它。
    _endthreadex同样也在THREADEX.C中,同样也给出伪代码:

    void __cdecl _endthreadex (unsigned retcode) {
         _ptiddata ptd;    // Pointer to thread's data block

         // Cleanup floating-point support (code not shown)

         // Get the address of this thread's tiddata block
         ptd = _getptd();

         // Free the tiddata block
         _freeptd(ptd);

         // Terminate the thread
         ExitThread(retcode);
    }

    注意CRT的_getptd函数在内部调用了系统的TlsGetValue函数来获取对应线程的tiddata内存块地址,然后释放该内存块,最后调用ExitThread来真正销毁线程,当然是用上面所提到的退出码来调用。
    我强烈建议你绝不要调用ExitThread来中止你的线程。最好也是最简单的办法就是让线程自己返回即可,让它自生自灭。ExitThread不仅徒增复杂,而且还会造成tiddata内存块泄漏。
    Microsoft Visual C++项目组发现人们总是喜欢调用ExitThread,他们希望能尽可能的做到让程序不泄漏内存。所以如果你真的想要明确地退出线程,你也最好使用_endthreadex,虽然这也不太好。
    OK,目前为止你应该对谁更好些的问题有了深入的了解,但是为什么调用CreateThread的程序仍然可以经年累月的正常运行呢?当线程调用一个需要tiddata结构的CRT函数时(大多数CRT函数是线程安全的,并不需要该结构),首先CRT函数试图获取线程的数据块的地址(通过调用TlsGetValue),然后,如果返回NULL,说明调用线程没有相关联的tiddata块,那么CRT函数马上为调用线程分配并初始化一个tiddata块,并将该内存块关联到线程(通过TlsSetValue),这样,该CRT函数以及其他CRT函数都可以使用该线程的tiddata块了(此即所谓“前人栽树后人乘凉”了,^_^)。
    当然,如果说你的线程运行的时候一直没有问题是几乎不可能的。事实上,的确有一些问题需要说说。如果线程使用了CRT的signal函数,整个进程都会被中止,因为结构化异常处理体尚未准备好。同样,如果不调用_endthreadex来中止线程就会造成内存泄漏,如果使用_beginthreadex,当然会容易想到_endthreadex,但如果你习惯了使用CreateThread,是否还会想起_endthreadex,我表示极大的怀疑,而且CreateThread/_endthreadex的组合怎么看怎么让人别扭。
    不要忘记开始的问题,接下来让我们再来看看效率问题。CRT库的多线程版本在某些函数里面放置了同步原语,比如malloc,为了保证堆不会被同时调用的malloc函数破坏,这不可避免地会对效率造成影响,C/C++的哲学我们不应忘记,“决不为自己没有用到的付出代价”,自然,我们无权要求单线程程序为多线程程序付出它们不该付出的代价,所以,开头的问题也有了答案。
    上面所说的都是静态链接的CRT库,而CRT库的动态链接版本则被编写得更加通用,以便能够被任何运行的程序和DLL共享。正是基于这个原因,这个版本的库只存在多线程版本。因为CRT库是以DLL形式提供的,程序和DLL不需要包含CRT库的任何代码,自然尺寸也就更小。同时,如果Microsoft修正了CRT库DLL中的Bug,程序也就自然受益了。
    终于该结束了,还是来几句总结吧:首先,如果你调用_beginthreadex,你会获得线程的句柄,句柄当然需要关闭,但_endthreadex并没有这么做。通常,是调用_beginthreadex的线程(很可能是主线程)来调用CloseHandle关闭不再需要的新线程的句柄。其次,如果你使用CRT函数,你只需要使用_beginthreadex即可。如果不使用,那么你可以只使用CreateThread。同样,如果只有一个线程(主线程)使用CRT,你也可以使用CreateThread;如果新创建的线程不使用CRT,那么你也不需要_beginthreadex和多线程CRT。

     
    展开全文
  • 你也许会说我一直CreateThread来创建线程,一直都工作得好好的,为什么_beginthreadex来代替CreateThread,下面让我来告诉你为什么。回答一个问题可以有两种方式,一种是简单的,一种是复杂的。如果你不愿意看...

    来源自自1999年7月MSJ杂志的《Win32 Q&A》栏目

    你也许会说我一直用CreateThread来创建线程,一直都工作得好好的,为什么要用_beginthreadex来代替CreateThread,下面让我来告诉你为什么。
    回答一个问题可以有两种方式,一种是简单的,一种是复杂的。
    如果你不愿意看下面的长篇大论,那我可以告诉你简单的答案:_beginthreadex在内部调用了CreateThread,在调用之前_beginthreadex做了很多的工作,从而使得它比CreateThread更安全。
    OK,下面是复杂的回答,^_^:
    微软在发布VC的同时附带了6个CRT库,下表列出了这些库的名称和详细描述:

    Library Name
    Description
    LIBC.LIB
    Statically linked library for single-threaded applications (this is the default library chosen when you create a new project).
    LIBCD.LIB
    Statically linked debug version of the library for single-threaded applications.
    LIBCMT.LIB
    Statically linked release version of the library for multithreaded applications.
    LIBCMTD.LIB
    Statically linked debug version of the library for multithreaded applications.
    MSVCRT.LIB
    Import library for dynamically linking the release version of the MSVCRT.DLL library. The library supports both single-threaded and multithreaded applications.
    MSVCRTD.LIB
    Import library for dynamically linking the debug version of the MSVCRT.DLL library. The library supports both single-threaded and multithreaded applications.

    在VC 6和VS 2003中可以在下图中所示的项目进行设置:


    jpg.gif此主题相关图片如下:
    按此在新窗口浏览图片

    jpg.gif此主题相关图片如下:
    按此在新窗口浏览图片

    为什么我们需要两个几乎相同的库来分别对待单线程和多线程程序?说起来也很简单,两个字——效率。让我们从头说起,标准CRT库出现于1970年左右,那时,线程的概念尚未出现在任何一个操作系统上。但是,线程毕竟是出现了,那好,让我们来看看下面这个例子,在这个例子中我们使用了CRT的全局变量errno:

    BOOL fFailure = (system("NOTEPAD.EXE README.TXT") == -1);

    if (fFailure) {
         switch (errno) {
         case E2BIG: // Argument list or environment too big
             break;

       case ENOENT: // Command interpreter cannot be found
           break;

       case ENOEXEC: // Command interpreter has bad format
           break;

       case ENOMEM: // Insufficient memory to run command
           break;
       }
    }

    设想这样的情况,当上面的代码执行到system函数之后,if声明之前的时候,操作系统打断了它,而转去执行进程中的另一个线程,而这个线程正好使用了会设置errno的某个CRT函数......于是,问题就出现了。
    为了解决这个问题,每个线程需要自己的errno全局变量,而且还需要一些机制来使得它们使用它们自己的errno变量,而不是其他线程的。当然,errno只是“多线程不服症”的其中一个受害者,其他受害者还有:_doserrno, strtok, _wcstok, strerror, _strerror, tmpnam, tmpfile, asctime, _wasctime, gmtime, _ecvt, _fcvt。
    于是,为了让C和C++程序能够正常工作,必须创建一个数据结构,并把它与每一个线程关连起来,只有这样才能调用CRT库时不至于误入“他线程家园”。
    那么系统怎么知道在创建一个新线程时分配这个数据块呢?回答是系统不知道,这一切责任都在你,只有你才能确保所有的事情正常完成。
    是不是有点重任在肩的感觉?呵呵,不要紧,其你要做的和标题所说的一样,只需要调用_beginthreadex函数即可:

    unsigned long _beginthreadex(void *security,
         unsigned stack_size,
         unsigned (*start_address)(void *), void *arglist,
         unsigned initflag, unsigned *thrdaddr);

    _beginthreadex的参数列表与CreateThread一模一样,只是参数名与类型有少许差异罢了。这是因为Microsoft觉得CRT函数不应该对Windows的数据类型有任何依赖。两者返回
    的东西也是一样的,所以即使你使用了CreateThread函数,要替换成_beginthreadex也是一件很容易的事情。
    因为两者的数据类型不完全一致,所以我们需要作一些转换来避免编译器的抱怨,为了简化这项工作,你可以使用我所写的这个宏:

    typedef unsigned (__stdcall *PTHREAD_START) (void *);

    #define chBEGINTHREADEX(psa, cbStack, pfnStartAddr, \
         pvParam, fdwCreate, pdwThreadID)                \
           ((HANDLE) _beginthreadex(                     \
              (void *) (psa),                            \
              (unsigned) (cbStack),                      \
              (PTHREAD_START) (pfnStartAddr),            \
              (void *) (pvParam),                        \
              (unsigned) (fdwCreate),                    \
              (unsigned *) (pdwThreadID)))

    注意_beginthreadex函数只存在于CRT库的多线程版本中,如果你链接到了一个单线程运行时库,链接器会毫不客气地报告“unresolved external symbol”错误。另外,还需要注意的是VS在创建新项目时默认选择的是单线程库,所以需要记得修改设置。
    说了这么多,只是说了一些概念,至于_beginthreadex为什么要比CreateThread更好,还是需要事实来说话的,当然,程序员所说的事实,就是代码了,代码之前,了无秘密,所以下面让我们来看看CRT库的代码是怎样的。
    首先,自然是主角人物_beginthreadex(你可以在THREADEX.C中找到它),因为没必要在这里重复写出源代码,所以我只给出伪代码版本的_beginthreadex:

    unsigned long __cdecl _beginthreadex (
         void *psa,
         unsigned cbStack,
         unsigned (__stdcall * pfnStartAddr) (void *),
         void * pvParam,
         unsigned fdwCreate,
         unsigned *pdwThreadID) {

         _ptiddata ptd;         // Pointer to thread's data block
         unsigned long thdl;    // Thread's handle

         // Allocate data block for the new thread
         if ((ptd = calloccrt(1, sizeof(struct tiddata))) == NULL)
             goto errorreturn;

         // Initialize the data block
         initptd(ptd);

         // Save the desired thread function and the parameter
         // we want it to get in the data block
         ptd->_initaddr = (void *) pfnStartAddr;
         ptd->_initarg = pvParam;

         // Create the new thread
         thdl = (unsigned long) CreateThread(psa, cbStack,
             _threadstartex, (PVOID) ptd, fdwCreate, pdwThreadID);
         if (thdl == NULL) {
             // Thread couldn't be created, cleanup and return failure
             goto error_return;
         }

         // Create created OK, return the handle
         return(thdl);

    error_return:
         // Error: data block or thread couldn't be created
         _free_crt(ptd);
         return((unsigned long)0L);
    }

    _beginthreadex的代码中有几个地方需要重点注意:
    首先每个线程会从CRT的堆上获得真正属于它自己的tiddata内存块。tiddata数据结构你可以在MTDLL.H中找到。传递给_beginthreadex的线程函数的地址被保存在tiddata内存块中。要传递给该线程函数的参数也被保存在这里。_beginthreadex接下来调用CreateThread,注意,这时CreateThread在新线程中执行的并不是pfnStartAddr函数,而是一个名为_threadstartex的函数。同时,传递给线程函数的参数也不是pvParam,而是tiddata结构的地址。最后,如果一切顺利将返回线程句柄,如果任何一个操作失败,将返回NULL。
    现在,tiddata结构已经被分配并初始化完成,下面来看看该结构是如何关联到线程的。这次的对象是_threadstartex,同样也在THREADEX.C中,同样也给出伪代码:

    static unsigned long WINAPI _threadstartex (void* ptd) {
        // Note: ptd is the address of this thread's tiddata block

        // Associate the tiddata block with this thread
        TlsSetValue(__tlsindex, ptd);

        // Save this thread ID in the tiddata block
        ((_ptiddata) ptd)->_tid = GetCurrentThreadId();

        // Initialize floating-point support (code not shown)

        // Wrap desired thread function in SEH frame to
        // handle runtime errors and signal support
        __try {
            // Call desired thread function passing it the desired parameter
            // Pass threads exit code value to _endthreadex
            _endthreadex(
              ( (unsigned (WINAPI *)(void *))(((_ptiddata)ptd)->_initaddr) )
                  ( ((_ptiddata)ptd)->_initarg ) ) ;
        }
        __except(_XcptFilter(GetExceptionCode(), GetExceptionInformation()){
            // The C-Runtime's exception handler deals with runtime errors
            // and signal support, we should never get it here.
            _exit(GetExceptionCode());
        }

        // We never get here, the thread dies in this function
        return(0L);
    }

    _threadstartex同样也有一些东西需要我们注意。新线程开始时会执行BaseThreadStart(位于Kernel32.DLL中),然后跳到_threadstartex。_threadstartex的唯一参数就是新线程的tiddata内存块地址。TlsSetValue完成了将tiddata结构与线程关联起来的目的(这里的tiddata结构被称为线程本地存储,TLS,顾名思义,就是属于每个线程自己的数据)。
    在事实上的线程函数周围放置了一个结构化异常处理体(A structured exception handling frame)。这个处理体主要负责处理与运行时库有关的很多东西,比如运行时错误(像抛出但却没有被捕获的C++异常这类东西)和CRT的signal函数。这很重要,如果你使用CreateThread创建了线程,然后又调用了CRT的signal函数,那么signal函数将无法正常工作。
    注意,这时还不能返回到BaseThreadStart,如果这样做,线程会死掉,退出码会正常设置,但tiddata内存块不会被销毁,这就会造成内存泄漏。为了防止泄漏,需要调用_endthreadex,并且将退出码传递给它。
    _endthreadex同样也在THREADEX.C中,同样也给出伪代码:

    void __cdecl _endthreadex (unsigned retcode) {
         _ptiddata ptd;    // Pointer to thread's data block

         // Cleanup floating-point support (code not shown)

         // Get the address of this thread's tiddata block
         ptd = _getptd();

         // Free the tiddata block
         _freeptd(ptd);

         // Terminate the thread
         ExitThread(retcode);
    }

    注意CRT的_getptd函数在内部调用了系统的TlsGetValue函数来获取对应线程的tiddata内存块地址,然后释放该内存块,最后调用ExitThread来真正销毁线程,当然是用上面所提到的退出码来调用。
    我强烈建议你绝不要调用ExitThread来中止你的线程。最好也是最简单的办法就是让线程自己返回即可,让它自生自灭。ExitThread不仅徒增复杂,而且还会造成tiddata内存块泄漏。
    Microsoft Visual C++项目组发现人们总是喜欢调用ExitThread,他们希望能尽可能的做到让程序不泄漏内存。所以如果你真的想要明确地退出线程,你也最好使用_endthreadex,虽然这也不太好。
    OK,目前为止你应该对谁更好些的问题有了深入的了解,但是为什么调用CreateThread的程序仍然可以经年累月的正常运行呢?当线程调用一个需要tiddata结构的CRT函数时(大多数CRT函数是线程安全的,并不需要该结构),首先CRT函数试图获取线程的数据块的地址(通过调用TlsGetValue),然后,如果返回NULL,说明调用线程没有相关联的tiddata块,那么CRT函数马上为调用线程分配并初始化一个tiddata块,并将该内存块关联到线程(通过TlsSetValue),这样,该CRT函数以及其他CRT函数都可以使用该线程的tiddata块了(此即所谓“前人栽树后人乘凉”了,^_^)。
    当然,如果说你的线程运行的时候一直没有问题是几乎不可能的。事实上,的确有一些问题需要说说。如果线程使用了CRT的signal函数,整个进程都会被中止,因为结构化异常处理体尚未准备好。同样,如果不调用_endthreadex来中止线程就会造成内存泄漏,如果使用_beginthreadex,当然会容易想到_endthreadex,但如果你习惯了使用CreateThread,是否还会想起_endthreadex,我表示极大的怀疑,而且CreateThread/_endthreadex的组合怎么看怎么让人别扭。
    不要忘记开始的问题,接下来让我们再来看看效率问题。CRT库的多线程版本在某些函数里面放置了同步原语,比如malloc,为了保证堆不会被同时调用的malloc函数破坏,这不可避免地会对效率造成影响,C/C++的哲学我们不应忘记,“决不为自己没有用到的付出代价”,自然,我们无权要求单线程程序为多线程程序付出它们不该付出的代价,所以,开头的问题也有了答案。
    上面所说的都是静态链接的CRT库,而CRT库的动态链接版本则被编写得更加通用,以便能够被任何运行的程序和DLL共享。正是基于这个原因,这个版本的库只存在多线程版本。因为CRT库是以DLL形式提供的,程序和DLL不需要包含CRT库的任何代码,自然尺寸也就更小。同时,如果Microsoft修正了CRT库DLL中的Bug,程序也就自然受益了。
    终于该结束了,还是来几句总结吧:首先,如果你调用_beginthreadex,你会获得线程的句柄,句柄当然需要关闭,但_endthreadex并没有这么做。通常,是调用_beginthreadex的线程(很可能是主线程)来调用CloseHandle关闭不再需要的新线程的句柄。其次,如果你使用CRT函数,你只需要使用_beginthreadex即可。如果不使用,那么你可以只使用CreateThread。同样,如果只有一个线程(主线程)使用CRT,你也可以使用CreateThread;如果新创建的线程不使用CRT,那么你也不需要_beginthreadex和多线程CRT。

    转载于:https://www.cnblogs.com/s5689412/archive/2006/12/05/582670.html

    展开全文
  • JAVA线程创建

    2020-01-30 21:30:30
    线程是什么 进程:一个程序的执行叫做一个进程 线程:程序中单个顺序的流控制称为线程,一个进程中可以...线程体是run()方法来实现的,线程启动后,系统自动调用run方法 例: 1、通过继承Thread类创建线程: 代码:...

    线程是什么

    进程:一个程序的执行叫做一个进程
    线程:程序中单个顺序的流控制称为线程,一个进程中可以含有多个线程,例如我们可以在任务栏中查看一个程序的线程数。

    查看线程数量
    一个进程中的多个线程是分享CPU,共享内存的。

    java语言是支持多线程的,比如java.lang中的类Thread。
    线程体是用run()方法来实现的,线程启动后,系统自动调用run方法
    例:
    1、通过继承Thread类创建线程:

    代码:
    class MyThread extends Thread{
             public void run(){
             for(int i;i=0;i++){
             System.out,println("调用线程")
             }
             }
    }
    

    2、通过向Thread()构造方法传递runable对象来创建线程

    代码:
    class MyThread implements Runnable{
             public void run(){…………}
    }
    Thread thread = new Thread(mytask);
    thread.start();
    
    

    3、用lambda表达式创建线程

    new Thread(() -> {……}).start;
    
    展开全文
  • windows核心编程-创建线程CreateThread

    千次阅读 2016-11-30 19:21:28
    2、首先从内核角度看,线程是一个内核对象,系统用它来村塾一些关于线程统计信息(比如时间) 3、从编程角度来看,线程是一堆寄存器状态以及线程栈的一个结构体对象,本质上可以理解为一个函数调用其( 寄存器状态...
  • 一、什么系统调用 接口:连接两个东西、信号转换、屏蔽细节…… 图形界面:消息框架程序+消息处理程序。 命令:一个c语言写的程序 操作系统接口:接口表示为函数调用,又由系统提供,所以称为系统调用。...
  • 【为什么线程?】 传统的图形用户界面应用程序都只有一个执行线程,并且一次只执行一个操作。如果用户从用户界面中调用一个比较耗时的操作,当该操作正在执行时,用户界面通常会冻结而不再响应。这个问题可以...

空空如也

空空如也

1 2 3 4 5 ... 13
收藏数 253
精华内容 101
关键字:

创建线程用什么系统调用