• 9.1 运用API创建多线程


    Windows平台下创建多线程有两种方式,读者可以使用CreateThread函数,或者使用beginthreadex函数均可,两者虽然都可以用于创建多线程环境,但还是存在一些差异的,首先CreateThread函数它是Win32 API的一部分,而_beginthreadexC/C++运行库的一部分,在参数返回值类型方面,CreateThread返回线程句柄,而_beginthreadex返回线程ID,当然这两者在使用上并没有太大的差异,但为了代码更加通用笔者推荐使用后者,因为后者与平台无关性更容易实现跨平台需求。

    9.1.1 CreateThread

    CreateThread 函数是Windows API提供的用于创建线程的函数。它接受一些参数,如线程的入口函数、线程的堆栈大小等,可以创建一个新的线程并返回线程句柄。开发者可以使用该句柄控制该线程的运行状态。需要注意,在使用CreateThread创建线程时,线程入口函数的返回值是线程的退出码,而不是线程执行的结果值。

    CreateThread 函数原型如下:

    HANDLE CreateThread(
      LPSECURITY_ATTRIBUTES   lpThreadAttributes,
      SIZE_T                  dwStackSize,
      LPTHREAD_START_ROUTINE  lpStartAddress,
      LPVOID                  lpParameter,
      DWORD                   dwCreationFlags,
      LPDWORD                 lpThreadId
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    参数说明:

    • lpThreadAttributes:指向SECURITY_ATTRIBUTES结构体的指针,指定线程安全描述符和访问权限。通常设为NULL,表示使用默认值。
    • dwStackSize:指定线程堆栈的大小,以字节为单位。如果dwStackSize为0,则使用默认的堆栈大小。(注:在32位程序下,该值的默认大小为1MB;在64位程序下,该值的默认大小为4MB)
    • lpStartAddress:指向线程函数的指针,这个函数就是线程执行的入口点。当线程启动时,系统就会调用这个函数。
    • lpParameter:指定传递给线程函数的参数,可以为NULL。
    • dwCreationFlags:指定线程的创建标志。通常设为0,表示使用默认值。
    • lpThreadId:指向一个DWORD变量的指针,表示返回的线程ID号。可以为NULL。

    CreateThread 函数将创建一个新的线程,并返回线程句柄。开发者可以使用该句柄控制该线程的运行状态,如挂起、恢复、终止等。线程创建成功后,执行线程函数进行相应的业务处理。需要注意的是,在使用CreateThread创建线程时,线程入口函数的返回值是线程的退出码,而不是线程执行的结果值。

    #include 
    #include 
    
    using namespace std;
    
    DWORD WINAPI Func(LPVOID lpParamter)
    {
      for (int x = 0; x < 10; x++)
      {
        cout << "thread function" << endl;
        Sleep(200);
      }
      return 0;
    }
    
    int main(int argc,char * argv[])
    {
      HANDLE hThread = CreateThread(NULL, 0, Func, NULL, 0, NULL);
      CloseHandle(hThread);
    
      for (int x = 0; x < 10; x++)
      {
        cout << "main thread" << endl;
        Sleep(400);
      }
    
      system("pause");
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    如上所示代码中我们在线程函数Func()内没有进行任何的加锁操作,那么也就会出现资源的争夺现象,这些会被抢夺的资源就被称为是临界资源,我们可以通过设置临界锁来实现同一时刻内保持一个线程操作资源。

    EnterCriticalSection 是Windows API提供的线程同步函数之一,用于进入一个临界区并且锁定该区域,以确保同一时间只有一个线程访问临界区代码。

    EnterCriticalSection函数的函数原型如下:

    void EnterCriticalSection(
      LPCRITICAL_SECTION lpCriticalSection
    );
    
    • 1
    • 2
    • 3

    参数说明:

    • lpCriticalSection:指向CRITICAL_SECTION结构体的指针,表示要进入的临界区。

    EnterCriticalSection 函数将等待,直到指定的临界区对象可用并且已经锁定,然后,当前线程将进入临界区。临界区中的代码将在当前线程完成之前,不允许被任何其他线程执行。当线程完成临界区的工作时,应该调用LeaveCriticalSection函数释放临界区。否则,其他线程将无法进入临界区,导致死锁。

    EnterCriticalSection 函数是比较底层的线程同步函数,需要开发者自行创建临界区,维护临界区的状态并进行加锁解锁的操作,使用时需要注意对临界区中的操作进行适当的封装和处理。同时,EnterCriticalSection函数也是比较高效的线程同步方式,对于需要频繁访问临界资源的场景,可以通过使用临界区来提高程序的性能。

    #include 
    #include 
    
    int Global_One = 0;
    
    // 全局定义临界区对象
    CRITICAL_SECTION g_cs;
    
    // 定义一个线程函数
    DWORD WINAPI ThreadProc(LPVOID lpParam)
    {
      // 加锁防止线程数据冲突
      EnterCriticalSection(&g_cs);
      for (int x = 0; x < 10; x++)
      {
        Global_One++;
        Sleep(1);
      }
    
      // 执行完修改以后,需要释放锁
      LeaveCriticalSection(&g_cs);
      return 0;
    }
    
    int main(int argc, char * argv[])
    {
      // 初始化临界区
      InitializeCriticalSection(&g_cs);
      HANDLE hThread[10] = { 0 };
    
      for (int x = 0; x < 10; x++)
      {
        // 循环创建线程
        hThread[x] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
      }
    
      // 等待多个线程执行结束
      WaitForMultipleObjects(10, hThread, TRUE, INFINITE);
    
      // 最后循环释放资源
      for (int x = 0; x < 10; x++)
      {
        CloseHandle(hThread[x]);
      }
    
      printf("全局变量值: %d \n", Global_One);
    
      // 释放锁
      DeleteCriticalSection(&g_cs);
    
      system("pause");
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    9.1.2 BeginThreadex

    BeginThreadex 是C/C++运行库提供的用于创建线程的函数。它也接受一些参数,如线程的入口函数、线程的堆栈大小等,与CreateThread不同的是,_beginthreadex函数返回的是线程的ID,而不是线程句柄。开发者可以使用该ID在运行时控制该线程的运行状态。此外,_beginthreadex函数通常与_endthreadex配对使用,供线程退出时使用。

    beginthreadex 函数的函数原型如下:

    uintptr_t _beginthreadex(
      void*                 security,
      unsigned             stack_size,
      unsigned(__stdcall*  start_address)(void*),
      void*                 arglist,
      unsigned             initflag,
      unsigned*            thrdaddr
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    参数说明:

    • security:与Windows安全机制相关,用于指定线程的安全属性,一般填NULL即可。
    • stack_size:指定线程的堆栈大小,以字节为单位。如果stack_size为0,则使用默认的堆栈大小。
    • start_address:线程函数的入口点。
    • arglist:传递给线程函数的参数。
    • initflag:线程标志,0表示启动线程后立即运行,CREATE_SUSPENDED表示启动线程后暂停运行。
    • thrdaddr:指向unsigned变量的指针,表示返回的线程ID号。可以为NULL。

    CreateThread相比,_beginthreadex函数返回线程ID而非线程句柄,使用时需要注意区分。与CreateThread不同的是,_beginthreadex函数接受传递给线程函数的参数放在arglist中,方便传递多个参数。线程使用完需要调用_endthreadex函数来关闭线程。当使用了_beginthreadex创建的线程退出时,会调用_endthreadex来结束线程,这里的返回值会被当做线程的退出码。

    #include 
    #include 
    #include 
    
    using namespace std;
    
    unsigned WINAPI Func(void *arg)
    {
      for (int x = 0; x < 10; x++)
      {
        cout << "thread function" << endl;
        Sleep(200);
      }
      return 0;
    }
    
    int main(int argc, char * argv[])
    {
      HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, Func, NULL, 0, NULL);
      CloseHandle(hThread);
      for (int x = 0; x < 10; x++)
      {
        cout << "main thread" << endl;
        Sleep(400);
      }
    
      system("pause");
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    由于CreateThread()函数是Windows提供的API接口,在C/C++语言另有一个创建线程的函数_beginthreadex()该函数在创建新线程时会分配并初始化一个_tiddata块,这个块用来存放一些需要线程独享的数据,从而保证了线程资源不会发生冲突的情况,代码只需要稍微在上面基础上改进即可。

    当然该函数同样需要设置线程临界区而设置方式与CreateThread中所展示的完全一致。

    #include 
    #include 
    #include 
    
    // 全局资源
    long g_nNum = 0;
    
    // 子线程个数
    const int THREAD_NUM = 10;
    
    CRITICAL_SECTION  g_csThreadCode;
    
    unsigned int __stdcall ThreadFunction(void *ptr)
    {
      int nThreadNum = *(int *)ptr;
    
      // 进入线程锁
      EnterCriticalSection(&g_csThreadCode);
      g_nNum++;
      printf("线程编号: %d --> 全局资源值: %d --> 子线程ID: %d \n", nThreadNum, g_nNum, GetCurrentThreadId());
    
      // 离开线程锁
      LeaveCriticalSection(&g_csThreadCode);
      return 0;
    }
    
    int main(int argc,char * argv[])
    {
      unsigned int ThreadCount = 0;
      HANDLE handle[THREAD_NUM];
    
      InitializeCriticalSection(&g_csThreadCode);
    
      for (int each = 0; each < THREAD_NUM; each++)
      {
        handle[each] = (HANDLE)_beginthreadex(NULL, 0, ThreadFunction, &each, 0, &ThreadCount);
        printf("线程ID: %d \n", ThreadCount);
      }
    
      WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
    
      DeleteCriticalSection(&g_csThreadCode);
    
      system("pause");
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    总的来说,_beginthreadexCreateThread更加高级,封装了许多细节,使用起来更方便,特别是对于传递多个参数的情况下,可以更简单地传参。

  • 相关阅读:
    springboot基于微信小程序的校园外卖系统毕业设计源码091024
    【Ctool】json 转 mysql
    RabbitMQ安装与简单使用
    uni-app 页面跳转动画
    C++ Reference: Standard C++ Library reference: C Library: cwchar: wcstold
    线段树合并
    C# Math 中的常用的数学运算
    PostgreSQL的学习心得和知识总结(一百三十九)|深入理解PostgreSQL数据库GUC参数 allow_alter_system 的使用和原理
    redis高可用、redis集群、redis缓存优化
    CentOSt7安装Redis错误:/bin/sh: cc: 未找到命令
  • 原文地址:https://blog.csdn.net/lyshark_csdn/article/details/133457482