在计算机科学中,线程被定义为进程中的一个单独的控制流。线程和进程的区别在于,每个进程都有自己的地址空间,而线程共享其所属进程的地址空间和其他资源。线程非常重要,因为它们可以执行并发操作,提高系统的吞吐量和运行效率。
线程的创建可以使用诸如pthread_create()、CreateThread()等系统调用。而在Windows操作系统中,线程的创建可以使用_beginthreadex函数。_beginthreadex函数是Windows下的线程库函数,由C运行时库提供,其功能与CreateThread()相似。
本文将详细介绍_beginthreadex函数的使用,通过实例让大家了解它的基本用法。
一、_beginthreadex函数的概述
C运行时库提供了许多线程功能,其中最常用的是_beginthreadex函数。本节将介绍_beginthreadex函数及其特性。
1. 函数原型
函数原型如下:
```c
unsigned int __stdcall _beginthreadex(
void* security,
unsigned stack_size,
unsigned (__stdcall* start_address)(void*),
void* arglist,
unsigned initflag,
unsigned* thrdaddr
);
```
其中参数各自的含义为:
- security: 线程安全描述参数,通常使用NULL表示默认权限。
- stack_size: 线程堆栈的大小,以字节为单位。通常使用0表示使用默认大小。
- start_address: 线程函数的地址。函数的参数必须从void*传递,并且返回一个unsigned整数。
- arglist: 传递给线程函数的一个指针。该参数可以为空。
- initflag: 控制线程的创建选项。通常使用0表示使用默认值。
- thrdaddr: 指向线程标识符末尾的指针。如果该参数不为空,则_beginthreadex函数返回新线程的标识符。
2. 返回值
实际上,_beginthreadex函数的返回值也是一个unsigned整数,该整数标识新创建线程的标识符。可以使用此标识符来控制和操作线程。
3. 线程函数的使用
我们必须写一个函数作为新线程的入口点。这个函数将被调用,执行在线程上下文中。
有一点需要注意,线程函数API不同于普通函数API。线程函数以unsigned (__stdcall*)为前缀而不是常规的__cdecl声明。
函数原型如下:
```c
unsigned __stdcall thread_func(void* arglist);
```
4. 线程同步
线程同步是指让线程按照特定的顺序或时间共享资源或执行任务。线程同步可通过互斥对象、信号量和事件三种机制来实现。
互斥对象是一种保护共享资源的同步对象。一个线程可以获得互斥对象的所有权,当它需要访问共享资源时,它可以使用该对象。当线程访问共享资源时,其他线程将被阻塞,直到获得互斥对象的线程完成为止。
信号量是一种与互斥对象类似的同步对象。信号量是一种整数计数器对象,它不仅可以用于保护共享资源,还可以用于控制线程访问它的时间。
事件是一种用于线程同步的同步对象。事件是由线程创建的一种同步基元,它可以分为两种类型:自动重置和手动重置。手动重置事件必须由线程手动复位,而自动重置事件则可以自动复位。
二、一个简单的_beginthreadex示例
这里有一个简单的_beginthreadex示例,该示例创建一个新线程来执行某些任务。请从以下代码中了解其实现方法:
```c
#include
#include
#include
#include
unsigned __stdcall thread_func(void* arglist);
void main() {
unsigned int threadid;
HANDLE thread_handle;
void* arglist = NULL;
thread_handle = (HANDLE)_beginthreadex(NULL, 0, &thread_func, arglist, 0, &threadid);
if(thread_handle == 0) {
printf("Error: Failed to create a new thread.\n");
exit(1);
}
WaitForSingleObject(thread_handle, INFINITE);
}
unsigned __stdcall thread_func(void* arglist) {
printf("A new thread is running now.\n");
printf("The thread id is %d.\n", GetCurrentThreadId());
printf("The process id is %d.\n", GetCurrentProcessId());
return 0;
}
```
在本例中,我们使用main()函数来启动新线程,该函数定义了以下步骤:
1. 定义了三个变量:线程标识符、线程句柄和一个指向参数列表的指针。线程句柄是一个Windows的同步对象。每个线程都有一个句柄来标识该线程。
2. 在_beginthreadex()函数中,我们初始化了一些参数:security使用了默认设置;stack_size使用了默认值0;start_address使用了thread_func()函数的地址;arglist传递了一个无效的值NULL;initflag使用了默认值0;返回值是指向已创建线程标识符的指针。
3. 然后,我们判断线程是否创建成功,如果成功,我们使用WaitForSingleObject函数等待线程完成执行。当我们使用INFINITE常量作为等待时间参数时,将永远等待线程完成执行。
在线程函数thread_func()中,我们打印了一条信息,并返回了0。
以上是一个简单的_beginthreadex示例。
三、多线程的使用
多线程有助于提高系统吞吐量和运行效率。不同的线程可以独立地执行不同的任务,从而缩短程序执行的时间。下面是一个多线程的使用示例,该示例使用两个线程并共享全局计数器:
```c
#include
#include
#include
#include
unsigned __stdcall thread_func(void* arglist);
int cnt = 0;
HANDLE lock;
void main() {
unsigned int threadid1, threadid2;
HANDLE thread_handle1, thread_handle2;
void* arglist = NULL;
lock = CreateMutex(NULL, FALSE, NULL);
thread_handle1 = (HANDLE)_beginthreadex(NULL, 0, &thread_func, arglist, 0, &threadid1);
thread_handle2 = (HANDLE)_beginthreadex(NULL, 0, &thread_func, arglist, 0, &threadid2);
if(thread_handle1 == 0 || thread_handle2 == 0) {
printf("Error: Failed to create a new thread.\n");
exit(1);
}
WaitForSingleObject(thread_handle1, INFINITE);
WaitForSingleObject(thread_handle2, INFINITE);
printf("The final count is %d\n", cnt);
CloseHandle(lock);
}
unsigned __stdcall thread_func(void* arglist) {
for(int i=0; i<10000; i++) {
WaitForSingleObject(lock, INFINITE);
cnt++;
ReleaseMutex(lock);
}
return 0;
}
```
在本例中,我们可以看到全局变量cnt表示总的计数器,两个线程将执行10000次计数,并直到自己完成计数后才会释放锁。
在main()函数中,我们首先创建互斥对象并将其赋值给lock。然后我们创建两个线程,并等待两个线程完成执行。
当两个线程完成执行时,我们打印了最终的计数值,并关闭了互斥对象。
在线程函数thread_func()中,我们使用互斥对象保护共享的计数器,并将其递增。
注意,如果把lock放在循环外进行创建,则互斥体创建多次。为了保证多次创建的恰好是同一个对象,所以应该在main函数中创建出lock后传递到线程函数中。
四、_beginthreadex函数和CreateThread函数的使用
_beginthreadex和CreateThead都是Windows下的线程库函数,因此它们之间有很多相似的地方。但也有一些主要的区别:
1. 返回值不同。_beginthreadex返回线程标识符,而CreateThread返回线程的句柄。
2. 销毁线程的方式也有所不同。使用_beginthreadex函数创建的线程可使用_endthreadex函数停止。使用CreateThread函数的线程可使用TerminateThread函数强制停止。
3. 线程函数的定义不同。使用_create_threadx()函数创建的线程函数需要返回一个unsigned整数,而使用CreateThread()函数创建的线程函数不需要。
总之,_beginthreadex函数是一种很好的线程库函数,在Windows操作系统中使用最广泛。在编写多线程应用程序时,_cleanthreadex函数可以帮助我们实现一个高效可靠的多线程环境。