1. 线程创建在进程的地址空间中,对于进程资源有着完全访问权限,多线程间共享资源,可自由通信;
2. 线程建立时也有自己的内核对象和线程栈,而非地址空间;
3. 一般需要实现多任务时我们更推荐使用线程实现,因为创建一个进程需要分配地址空间,而且进程间的切换也很不方便,即使用进程开销很高;
一、创建多线程
简单了解了进程与线程的概念之后,我们来看看如何在程序中创建线程。Windows SDK为我们提供了创建线程的专用函数:CreateThread()
点击(此处)折叠或打开
- HANDLE CreateThread(
- LPSECURITY_ATTRIBUTES lpsa,
- DWORD cbStack,
- LPTHREAD_START_ROUTINE lpStartAddr,
- LPVOID lpvThreadParam,
- DWORD fdwCreate,
- LPDWORD lpIDThread
- );
-2-cbStack表示的线程初始栈的大小,若使用0则表示采用默认大小初始化;
-3-lpStartAddr表示线程开始的位置,即线程要执行的函数代码,这点有点类似于回调函数的使用;
-4-lpvThreadParam用来接收线程过程函数的参数,不需要时可以设置为NULL;
-5-fdwCreate表示创建线程时的标志,CREATE_SUSPENDED表示线程创建后挂起暂不执行,必须调用ResumeThread才可以执行,0表示线程创建之后立即执行
-6-lpIDThread用来保存线程的ID;
了解了CreateThread函数的用法,我们来看一个例子实际体验一下。下面的例子会开启一个线程循环输出信息,我们可以查看结果:
点击(此处)折叠或打开
- //MultiThread
- #include <iostream>
- #include <cstdlib>
- #include <windows.h>
- using namespace std;
- DWORD WINAPI Fun1Proc(LPVOID lpParameter);
- int main()
- {
- int j = 0;
-
- HANDLE hThread_1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
- CloseHandle(hThread_1);
-
- while (j++ < 1000)
- cout << "MainThread is running for" << " the "<< j <<" times "<<endl;
-
- system("pause");
- return 0;
-
-
- }
- DWORD WINAPI Fun1Proc(LPVOID lpParameter)
- {
- int i = 0;
- while (i++ < 1000)
- cout << "Thread 1 is running for" <<" the "<< i <<" times "<<endl;
-
- return 0;
- }
二、多线程的问题
上面的程序我们只是建立一个新线程,如果建立两个呢?下面我们模拟一个火车售票的模型,线程1和线程2同时负责售票,主线程负责平台搭建。线程1和线程2分别访问全局变量tickets,输出其值作为票号然后将其值减一,直至“售完”所有票:
点击(此处)折叠或打开
- //MultiThread
- #include <iostream>
- #include <cstdlib>
- #include <windows.h>
- using namespace std;
- DWORD WINAPI Fun1Proc(LPVOID lpParameter);
- DWORD WINAPI Fun2Proc(LPVOID lpParameter);
- int tickets = 100;
- int main()
- {
- HANDLE hThread_1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
- HANDLE hThread_2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
- CloseHandle(hThread_1);
- CloseHandle(hThread_2);
- system("pause");
- return 0;
-
-
- }
- DWORD WINAPI Fun1Proc(LPVOID lpParameter)
- {
- while (true)
- {
- if (tickets > 0)
- {
- Sleep(10);
- cout << "Thread 1 sell ticket : "<<tickets--<<endl;
- }
- else
- break;
- }
- return 0;
- }
-
- DWORD WINAPI Fun2Proc(LPVOID lpParameter)
- {
- while (true)
- {
-
- if (tickets > 0)
- {
- Sleep(10);
- cout << "Thread 2 sell ticket : "<<tickets--<<endl;
- }
- else
- break;
-
- }
- return 0;
- }
分析下上面的结果,首先线程1和线程2确实交替运行“购票”了,其次看到不规则的输出,1314连到一起。关于这点我们要知道由于CPU是执行分片处理的,即不同的线程会得到不同的时间片来执行程序,尤其是对于单CPU来说更是如此,因此当线程1执行到"Thread 1 sell ticket"时结束分片,由线程2继续执行;线程2执行到同样位置再次交还给线程1继续执行显示“13”,然后线程2执行显示“14”。这样看好像也没有问题,但是问题在于最后出现了“0”,我们的代码中是不允许出现0的,之所以这样,同上面的元婴一样,在线程1执行到ticket = 1时交接给了线程2执行,并且输出了其值1,然后线程1继续输出了当前值0.此时的if条件就失去作用了。虽然这种情况不一定总是发生,但是在实际的操作中是很有可能出现的,而且排查起来也很困难。那么我们该如何解决呢?
问题的关键在于线程1和线程2同时访问了全局变量tickets,导致了错误;那么我们的解决方案就是将全局变量的访问控制起来就可以了,不允许同时有多个线程同时访问该变量。我们使用互斥量来实现。
三、互斥量的应用
使用互斥量并不困难,核心步骤如下:
-1-CreateMutex创建一个互斥量:
点击(此处)折叠或打开
- HANDLE CreateMutex(
- LPSECURITY_ATTRIBUTES lpMutexAttributes,
- BOOL bInitialOwner,
- LPCTSTR lpName
- );
-2-WaitForSingleObject():请求一个互斥量的访问权;
-3-ReleaseMutex():释放一个互斥量的访问权;
好了,我们再来看看应用了互斥量的改进程序:
点击(此处)折叠或打开
- //MultiThread
- #include <iostream>
- #include <cstdlib>
- #include <windows.h>
- using namespace std;
- DWORD WINAPI Fun1Proc(LPVOID lpParameter);
- DWORD WINAPI Fun2Proc(LPVOID lpParameter);
- int tickets = 100;
- HANDLE hMutex;
- int main()
- {
- hMutex = CreateMutex(NULL, FALSE, NULL);
-
- HANDLE hThread_1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
- HANDLE hThread_2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
- CloseHandle(hThread_1);
- CloseHandle(hThread_2);
-
- system("pause");
- return 0;
-
-
- }
- DWORD WINAPI Fun1Proc(LPVOID lpParameter)
- {
- while (true)
- {
- WaitForSingleObject(hMutex, INFINITE);
- if (tickets > 0)
- {
- Sleep(10);
- cout << "Thread 1 sell ticket : "<<tickets--<<endl;
- }
- else
- break;
- ReleaseMutex(hMutex);
- }
- return 0;
- }
-
- DWORD WINAPI Fun2Proc(LPVOID lpParameter)
- {
- while (true)
- {
- WaitForSingleObject(hMutex, INFINITE);
- if (tickets > 0)
- {
- Sleep(10);
- cout << "Thread 2 sell ticket : "<<tickets--<<endl;
- }
- else
- break;
- ReleaseMutex(hMutex);
- }
- return 0;
- }
互斥量还有一个小应用,利用命名互斥量来保证只有一个程序实例运行。我们可以创建一个命名互斥量,当程序要重复运行时,检查互斥量的返回值,若为ERROR_ALREADY_EXISTS则表示已经有一个实例运行了,直接return即可。在源程序中添加以下代码:
点击(此处)折叠或打开
- //确保只有一个实例运行
- HANDLE hMutex_1 = CreateMutex(NULL, TRUE, "tickets");
- if (hMutex_1)
- {
- if (ERROR_ALREADY_EXISTS == GetLastError())
- {
- cout << "Only one instance can run !" << endl;
- system("pause");
- return 0;
- }
- }