Windows 多线程学习一

来源:互联网 发布:钢筋翻样软件 编辑:程序博客网 时间:2024/05/31 18:30

1.关于win32系统下创建线程数目的学习:

默认情况下,一个线程的栈要预留1M的内存空间而一个进程中可用的内存空间只有2G,所以理论上一个进程中最多可以开2048个线程。 但是内存当然不可能完全拿来作线程的栈,所以实际数目要比这个值要小。 你也可以通过连接时修改默认栈大小,将其改的比较小,这样就可以多开一些线程。 如将默认栈的大小改成512K,这样理论上最多就可以开4096个线程。 即使物理内存再大,一个进程中可以起的线程总要受到2GB这个内存空间的限制。 比方说你的机器装了64GB物理内存,但每个进程的内存空间还是4GB,其中用户态可用的还是2GB。如果是同一台机器内的话,能起多少线程也是受内存限制的。每个线程对象都要站用非页面内存,而非页面内存也是有限的,当非页面内存被耗尽时,也就无法创建线程了。 如果物理内存非常大,同一台机器内可以跑的线程数目的限制值会越来越大。  

在Windows下写个程序,一个进程Fork出2000个左右线程就会异常退出了,为什么?

这个问题的产生是因为windows32位系统,一个进程所能使用的最大虚拟内存为2G,而一个线程的默认线程栈StackSize为1024K(1M),这样当线程数量逼近2000时,2000*1024K=2G(大约),内存资源就相当于耗尽。

MSDN原文:

“The number of threads a process can create is limited by the available virtual memory. By default, every thread has one megabyte of stack space. Therefore, you can create at most 2,028 threads. If you reduce the default stack size, you can create more threads. However, your application will have better performance if you create one thread per processor and build queues of requests for which the application maintains the context information. A thread would process all requests in a queue before processing requests in the next queue.”

如何突破2000个限制?

可以通过修改CreateThread参数来缩小线程栈StackSize,例如

<pre name="code" class="java">#define   MAX_THREADS   50000 DWORD   WINAPI   ThreadProc(   LPVOID   lpParam   ){while(1){Sleep(100000);}return   0;} int   main()   {DWORD   dwThreadId[MAX_THREADS];HANDLE   hThread[MAX_THREADS]; for(int   i   =   0;   i   <   MAX_THREADS;   ++i){hThread[i]  = CreateThread(0,  64, ThreadProc, 0, STACK_SIZE_PARAM_IS_A_RESERVATION,   &dwThreadId[i]); if(0   ==   hThread[i]){DWORD   e   =   GetLastError();printf("%d\r\n",e);break;}}ThreadProc(0);}


2.CreateThread与CloseHandle

CreateThread用于创建一个线程,其函数原型如下:

HANDLE WINAPI CreateThread(    LPSECURITY_ATTRIBUTES   lpThreadAttributes, //线程安全相关的属性,常置为NULL    SIZE_T                  dwStackSize,        //新线程的初始化栈在大小,可设置为0    LPTHREAD_START_ROUTINE  lpStartAddress,     //被线程执行的回调函数,也称为线程函数    LPVOID                  lpParameter,        //传入线程函数的参数,不需传递参数时为NULL    DWORD                   dwCreationFlags,    //指定额外的标志来控制线程的创建,为0表示线程创建之后立即                                                   就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂                                                  停运行,这样它就无法调度,直到调用ResumeThread()。    LPDWORD                 lpThreadId          //传出参数,用于获得线程ID,如果为NULL则不返回线程ID);

**说明:**lpThreadAttributes:指向SECURITY_ATTRIBUTES结构的指针,决定返回的句柄是否可被子进程继承,如果为NULL则表示返回的句柄不能被子进程继承。

dwStackSize :线程栈的初始化大小,字节单位。系统分配这个值1M,可修改如上所示。

lpStartAddress:指向一个函数指针,该函数将被线程调用执行。因此该函数也被称为线程函数(ThreadProc),是线程执行的起始地址,线程函数是一个回调函数,由操作系统在线程中调用。线程函数的原型如下:


DWORD WINAPI ThreadProc(LPVOID lpParameter);    //lpParameter是传入的参数,是一个空指针
lpParameter:传入线程函数(ThreadProc)的参数,不需传递参数时为NULL
dwCreationFlags:控制线程创建的标志,有三个类型,0:线程创建后立即执行线程;CREATE_SUSPENDED:线程创建后进入就绪状态,直到线程被唤醒时才调用;STACK_SIZE_PARAM_IS_A_RESERVATION:dwStackSize 参数指定线程初始化栈的大小,如果STACK_SIZE_PARAM_IS_A_RESERVATION标志未指定,dwStackSize将会设为系统预留的值。 

【Demo1】:创建一个最简单的线程

#include "stdafx.h"#include <windows.h>#include <iostream>using namespace std;//线程函数DWORD WINAPI ThreadProc(LPVOID lpParameter){    for (int i = 0; i < 5; ++ i)    {        cout << "子线程:i = " << i << endl;        Sleep(100);    }    return 0L;}int main(){    //创建一个线程    HANDLE thread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);    //关闭线程    CloseHandle(thread);    //主线程的执行路径    for (int i = 0; i < 5; ++ i)    {        cout << "主线程:i = " << i << endl;        Sleep(100);    }    return 0;}结果如下: 主线程:i = 0 子线程:i = 0 主线程:i = 1 子线程:i = 1 子线程:i = 2 主线程:i = 2 子线程:i = 3 主线程:i = 3 子线程:i = 4 主线程:i = 4


【Demo2】:在线程函数中传入参数

#include "stdafx.h"#include <windows.h>#include <iostream>using namespace std;#define NAME_LINE   40//定义线程函数传入参数的结构体typedef struct __THREAD_DATA{    int nMaxNum;    char strThreadName[NAME_LINE];    __THREAD_DATA() : nMaxNum(0)    {        memset(strThreadName, 0, NAME_LINE * sizeof(char));    }}THREAD_DATA;//线程函数DWORD WINAPI ThreadProc(LPVOID lpParameter){    THREAD_DATA* pThreadData = (THREAD_DATA*)lpParameter;    for (int i = 0; i < pThreadData->nMaxNum; ++ i)    {        cout << pThreadData->strThreadName << " --- " << i << endl;        Sleep(100);    }    return 0L;}int main(){    //初始化线程数据    THREAD_DATA threadData1, threadData2;    threadData1.nMaxNum = 5;    strcpy(threadData1.strThreadName, "线程1");    threadData2.nMaxNum = 10;    strcpy(threadData2.strThreadName, "线程2");//创建第一个子线程    HANDLE hThread1 = CreateThread(NULL, 0, ThreadProc, &threadData1, 0, NULL);    //创建第二个子线程    HANDLE hThread2 = CreateThread(NULL, 0, ThreadProc, &threadData2, 0, NULL);    //关闭线程    CloseHandle(hThread1);    CloseHandle(hThread2);    //主线程的执行路径    for (int i = 0; i < 5; ++ i)    {        cout << "主线程 === " << i << endl;        Sleep(100);    }    system("pause");    return 0;}结果:主线程 === 线程1 — 0 0 线程2 — 0 线程1 — 1 主线程 === 1 线程2 — 1 主线程 === 2 线程1 — 2 线程2 — 2 主线程 === 3 线程2 — 3 线程1 — 3 主线程 === 4 线程2 — 4 线程1 — 4 线程2 — 5 请按任意键继续… 线程2 — 6 线程2 — 7 线程2 — 8 线程2 — 9

3.CreateMutex、WaitForSingleObject、ReleaseMutex
从【Demo2】中可以看出,虽然创建的子线程都正常执行起来了,但输出的结果并不是我们预期的效果。我们预期的效果是每输出一条语句后自动换行,但结果却并非都是这样。这是因为在线程执行时没有做同步处理,比如第一行的输出,主线程输出“主线程 ===”后时间片已用完,这时轮到子线程1输出,在子线程输出“线程1 —”后时间片也用完了,这时又轮到主线程执行输出“0”,之后又轮到子线程1输出“0”。于是就出现了“主线程 === 线程1 — 0 0”的结果。


主线程:cout << “主线程 === ” << i << endl; 
子线程:cout << pThreadData->strThreadName << ” — ” << i << endl;


为避免出现这种情况,我们对线程做一些简单的同步处理,这里我们用互斥量(Mutex),关于互斥量(Mutex)的概念,请看《编程思想之多线程与多进程(2)——线程优先级与线程安全》一文;更多C++线程同步的处理,请看下一节。


在使用互斥量进行线程同步时会用到以下几个函数:


HANDLE WINAPI CreateMutex(
    LPSECURITY_ATTRIBUTES lpMutexAttributes,        //线程安全相关的属性,常置为NULL
    BOOL                  bInitialOwner,            //创建Mutex时的当前线程是否拥有Mutex的所有权
    LPCTSTR               lpName                    //Mutex的名称
);
**说明:**lpMutexAttributes也是表示安全的结构,与CreateThread中的lpThreadAttributes功能相同,表示决定返回的句柄是否可被子进程继承,如果为NULL则表示返回的句柄不能被子进程继承。bInitialOwner表示创建Mutex时的当前线程是否拥有Mutex的所有权,若为TRUE则指定为当前的创建线程为Mutex对象的所有者,其它线程访问需要先ReleaseMutex。lpName为Mutex的名称。


DWORD WINAPI WaitForSingleObject(
    HANDLE hHandle,                             //要获取的锁的句柄
    DWORD  dwMilliseconds                           //超时间隔
);
**说明:**WaitForSingleObject的作用是等待一个指定的对象(如Mutex对象),直到该对象处于非占用的状态(如Mutex对象被释放)或超出设定的时间间隔。除此之外,还有一个与它类似的函数WaitForMultipleObjects,它的作用是等待一个或所有指定的对象,直到所有的对象处于非占用的状态,或超出设定的时间间隔。 
hHandle:要等待的指定对象的句柄。dwMilliseconds:超时的间隔,以毫秒为单位;如果dwMilliseconds为非0,则等待直到dwMilliseconds时间间隔用完或对象变为非占用的状态,如果dwMilliseconds 为INFINITE则表示无限等待,直到等待的对象处于非占用的状态。


BOOL WINAPI ReleaseMutex(HANDLE hMutex);
说明:释放所拥有的互斥量锁对象,hMutex为释放的互斥量的句柄。

SaleTickets.h :#include "stdafx.h"#include <windows.h>#include <iostream>#include <strstream> #include <string>using namespace std;#define NAME_LINE   40//定义线程函数传入参数的结构体typedef struct __TICKET{    int nCount;    char strTicketName[NAME_LINE];    __TICKET() : nCount(0)    {        memset(strTicketName, 0, NAME_LINE * sizeof(char));    }}TICKET;typedef struct __THD_DATA{    TICKET* pTicket;    char strThreadName[NAME_LINE];    __THD_DATA() : pTicket(NULL)    {        memset(strThreadName, 0, NAME_LINE * sizeof(char));    }}THD_DATA; //基本类型数据转换成字符串template<class T>string convertToString(const T val){    string s;    std::strstream ss;    ss << val;    ss >> s;    return s;}//售票程序DWORD WINAPI SaleTicket(LPVOID lpParameter);SaleTickets.cpp :#include "stdafx.h"#include <windows.h>#include <iostream>#include "SaleTickets.h"using namespace std;extern HANDLE g_hMutex;//售票程序DWORD WINAPI SaleTicket(LPVOID lpParameter){    THD_DATA* pThreadData = (THD_DATA*)lpParameter;    TICKET* pSaleData = pThreadData->pTicket;    while(pSaleData->nCount > 0)    {        //请求获得一个互斥量锁        WaitForSingleObject(g_hMutex, INFINITE);        if (pSaleData->nCount > 0)        {            cout << pThreadData->strThreadName << "出售第" << pSaleData->nCount -- << "的票,";            if (pSaleData->nCount >= 0) {                cout << "出票成功!剩余" << pSaleData->nCount << "张票." << endl;            } else {                cout << "出票失败!该票已售完。" << endl;            }        }        Sleep(10);        //释放互斥量锁        ReleaseMutex(g_hMutex);    }    return 0L;}测试程序://售票系统void Test2(){    //创建一个互斥量    g_hMutex = CreateMutex(NULL, FALSE, NULL);    //初始化火车票    TICKET ticket;    ticket.nCount = 100;    strcpy(ticket.strTicketName, "北京-->赣州");    const int THREAD_NUMM = 8;    THD_DATA threadSale[THREAD_NUMM];    HANDLE hThread[THREAD_NUMM];    for(int i = 0; i < THREAD_NUMM; ++ i)    {        threadSale[i].pTicket = &ticket;        string strThreadName = convertToString(i);        strThreadName = "窗口" + strThreadName;        strcpy(threadSale[i].strThreadName, strThreadName.c_str());        //创建线程        hThread[i] = CreateThread(NULL, NULL, SaleTicket, &threadSale[i], 0, NULL);        //请求获得一个互斥量锁        WaitForSingleObject(g_hMutex, INFINITE);        cout << threadSale[i].strThreadName << "开始出售 " << threadSale[i].pTicket->strTicketName << " 的票..." << endl;        //释放互斥量锁        ReleaseMutex(g_hMutex);        //关闭线程        CloseHandle(hThread[i]);    }    system("pause");



0 0
原创粉丝点击