多线程CreateThread函数的用法

2022-09-06 16:16:01 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

代码语言:javascript复制
CreateThread
当使用CreateProcess调用时,系统将创建一个进程和一个主线程。CreateThread将在主线程的基础上创建一个新线程,大致做如下步骤:
  1在内核对象中分配一个线程标识/句柄,可供管理,由CreateThread返回
  2把线程退出码置为STILL_ACTIVE,把线程挂起计数置1
  3分配context结构
  4分配两页的物理存储以准备栈,保护页设置为PAGE_READWRITE,第2页设为PAGE_GUARD
  5lpStartAddr和lpvThread值被放在栈顶,使它们成为传送给StartOfThread的参数
  6把context结构的栈指针指向栈顶(第5步)指令指针指向startOfThread函数
代码语言:javascript复制
HANDLE WINAPI CreateThread(
    LPSECURITY_ATTRIBUTES   lpThreadAttributes, //线程安全相关的属性,常置为NULL
    SIZE_T                  dwStackSize,        //新线程的初始化栈在大小,可设置为0
    LPTHREAD_START_ROUTINE  lpStartAddress,     //被线程执行的回调函数,也称为线程函数
    LPVOID                  lpParameter,        //传入线程函数的参数,不需传递参数时为NULL
    DWORD                   dwCreationFlags,    //控制线程创建的标志
    LPDWORD                 lpThreadId          //传出参数,用于获得线程ID,如果为NULL则不返回线程ID
);

第一个参数是指向SECURITY_ATTRIBUTES型态的结构的指针。在Windows 98中忽略该参数。在Windows NT中,它被设为NULL。 第二个参数是用于新线程的初始堆栈大小,默认值为0。在任何情况下,Windows根据需要动态延长堆栈的大小。 第三个参数是指向线程函数的指标。函数名称没有限制,但是必须以下列形式声明: DWORD WINAPI ThreadProc (PVOID pParam) ; 第四个参数为传递给ThreadProc的参数。这样主线程和从属线程就可以共享数据。 第五个参数通常为0,但当建立的线程不马上执行时为旗标CREATE_SUSPENDED。线程将暂停直到呼叫ResumeThread来恢复线程的执行为止。表示创建线程的运行状态,其中CREATE_SUSPEND表示挂起当前创建的线程,而0表示立即执行当前创建的进程; 第六个参数 lpThreadID:返回新创建的线程的ID编号;是一个指标,指向接受执行绪ID值的变量。

如果函数调用成功,则返回新线程的句柄,调用WaitForSingleObject函数等待所创建线程的运行结束。函数的格式如下:

参数的含义如下: hHandle:指定对象或时间的句柄;

dwMilliseconds:等待时间,以毫秒为单位,当超过等待时间时,此函数返回。如果参数设置为0,则该函数立即返回;如果设置成INFINITE,则该函数直到有信号才返回。

一般情况下需要创建多个线程来提高程序的执行效率,但是多个线程同时运行的时候可能调用线程函数,在多个线程同时对一个内存地址进行写入操作,由于CPU时间调度的问题,写入的数据会被多次覆盖,所以要使线程同步。

就是说,当有一个线程对文件进行操作时,其它线程只能等待。可以通过临界区对象实现线程同步。临界区对象是定义在数据段中的一个CRITICAL_SECTION结构,Windows内部使用这个结构记录一些信息,确保同一时间只有一个线程访问改数据段中的数据。

使用临界区的步骤如下:

(1)初始化一个CRITICAL_SECTION结构;在使用临界区对象之前,需要定义全局CRITICAL_SECTION变量,在调用CreateThread函数前调用InitializeCriticalSection函数初始化临界区对象;

(2)申请进入一个临界区;在线程函数中要对保护的数据进行操作前,可以通过调用EnterCriticalSection函数申请进入临界区。由于同一时间内只能有一个线程进入临界区,所以在申请的时候如果有一个线程已经进入临界区,则该函数就会一直等到那个线程执行完临界区代码;

(3)离开临界区;当执行完临界区代码后,需要调用LeaveCriticalSection函数离开临界区;

(4)删除临界区;当不需要临界区时调用DeleteCriticalSection函数将临界区对象删除;

下面的代码创建了5个线程,每个线程在文件中写入10000个“hello”:

代码语言:javascript复制
DWORD WaitForSingleObject(
                          HANDLE hHandle,
                          DWORD dwMilliseconds
                         );

注意:临界区要在线程执行前初始化,因为线程一但被建立即开始运行(除非手工挂起),但线程建立后在初始化临界区可能出现问题

代码语言:javascript复制
#include <stdlib.h>
#include <iostream>
#include <list>
#include <conio.h>
#include <time.h>
#include <algorithm>
#include <windows.h>

using namespace std;
 
//volatile是一个类型修饰符,表示这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了
volatile int b = 0;
//int b = 0;
 
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
    int i = 10000;
    int *p = (int*)lpParameter;
    while(i--)
    {
        (*p)  ;
        b  ;
    }
 
    return 0;
}
 
int main(int argc, char* argv[])
{
    int a = 0;
 
    HANDLE hThread1 = CreateThread(NULL, 0, ThreadProc, &a, 0, NULL);
    HANDLE hThread2 = CreateThread(NULL, 0, ThreadProc, &a, 0, NULL);
    HANDLE hThread3 = CreateThread(NULL, 0, ThreadProc, &a, 0, NULL);
    HANDLE hThread4 = CreateThread(NULL, 0, ThreadProc, &a, 0, NULL);
    HANDLE hThread5 = CreateThread(NULL, 0, ThreadProc, &a, 0, NULL);
 
    Sleep(1000);
 
    CloseHandle(hThread1);
    CloseHandle(hThread2);
    CloseHandle(hThread3);
    CloseHandle(hThread4);
    CloseHandle(hThread5);
 
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
 
    system("pause");
    return 0;
}

每次运行 得到的结果有可能不一致

a = 43955 b = 42426

//

a = 50000 b = 50000

回调函数必须是静态成员函数或全局函数 ,不放在类里,不必static,

放在类里一定要static

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/154948.html原文链接:https://javaforall.cn

0 人点赞