C++多线程学习笔记(三)

前两篇多线程笔记可以不用看,这个是更系统更详细的整理。

每次使用多线程时,总有些细节问题不清楚,这里从基础部分开始整理一下,以便后续进行学习和使用。

机器不同,系统给每个线程分配的时间片和运行机制也不同。我这里是基于win10系统的VS2010的win32控制台应用程序做的。运行结果,会与孙鑫视频课中的有些不同,所以有些代码稍微调整,比如Sleep(1)的位置。

一、最简单的多线程原型

#include <Windows.h>// 使用系统API函数,所以需要包含此头文件
#include <iostream>
using namespace std;

DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
	cout << "thread1 is running" << endl;
	return 0;
}

int main()
{
    HANDLE hThread1;
    hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
    // 创建之后,关闭线程句柄。
    // 表明主线程不需要引用刚创建的线程,但是线程依然在运行。
    // 让线程的引用计数减一,当线程结束,计数就能为0,自动清空释放。
    // 否则只能等整个进程计数后,才释放内核对象
    CloseHandle(hThread1);
    // 这个时间,是为了让线程输出,如果设置的小,不等线程输出完,主线程就会先输出了,然后线程继续输出剩下的部分。
    Sleep(10);
    cout << "main thread!" << endl;
    system("pause");
    return 0;
}

当sleep 10ms时,线程能完整输出。当sleep 1ms 时,线程的回车不能输出。 如果线程输出内容比较多,还会被截断。所以需要控制好主线程的Sleep()的时间。

   

 二、测试系统为多线程分配的时间片

系统不同,那么每个线程得到的时间片长短也不同。

#include <Windows.h>// 使用系统API函数,所以需要包含此头文件
#include <iostream>
using namespace std;

int index = 1;

DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
    while(index++ < 1000)
    {
	    cout << "thread1 is running" << endl;
    }
	return 0;
}

int main()
{
    HANDLE hThread1;
    hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
    // 创建之后,关闭线程句柄。
    // 表明主线程不需要引用刚创建的线程,但是线程依然在运行。
    // 让线程的引用计数减一,当线程结束,计数就能为0,自动清空释放。
    // 否则只能等整个进程计数后,才释放内核对象
    CloseHandle(hThread1);
    // 这个时间,是为了让线程输出,如果设置的小,不等线程输出完,主线程就会先输出了,然后线程继续输出剩下的部分。
   // Sleep(10);
    while(index++ < 1000)
        cout << "main thread!" << endl;
    system("pause");
    return 0;
}

从结果,可以看出,两个时间片由系统分配,然后主线程和线程,按照各自得到的时间片交替输出。

 三、开辟两个线程,模拟售卖火车票系统

#include <Windows.h>// 使用系统API函数,所以需要包含此头文件
#include <iostream>
using namespace std;

int tickets = 100;
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
	while(true)
	{
		if (tickets > 0)
		{
			Sleep(1);
			cout << "thread1 sell ticket : "  << tickets--<< endl;
		}
		else
		{
			break;
		}
		
	}
	return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
	while(true)
	{
		if (tickets > 0)
		{
			Sleep(1);
			cout << "thread2 sell ticket : "  << tickets--<< endl;
		}
		else
		{
			break;
		}
	}
	return 0;
}
int main()
{
	HANDLE hThread1, hThread2;
	hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
	hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
	CloseHandle(hThread1);
	CloseHandle(hThread2);
	Sleep(1000);// 让上面两个线程运行完之后,主线程再结束
	system("pause");
	return 0;
}

运行结果如下。当tickets=1时,进入1线程,1线程Sleep(1), 2线程开始卖票,然后2线程Sleep(1), 又将执行权交给1线程, 1线程卖完,剩0张票, 所以2线程,就卖0票了。但是在运行过程中,并没有出现这个结果,可能是系统自己处理了最后的判断。定位跟踪了一下,发现确实,到tickets=1时,Sleep(1);就被跳过了,不再睡眠,直接在这一个线程内执行完毕。

虽然不会出现买0张票,当设置tickets=3,单步跟踪时,出现了两个线程同时买2号票,所以还是有问题的。为了安全起见,还是需要互斥或者线程锁的,下面依次进行介绍三种互斥或线程锁的方法。 

四、线程互斥CreateMutex

HANDLE hMutex; 相当于一把钥匙,谁拿到了,就先用,等用完了,把钥匙还给系统,下一个线程再取钥匙,进行使用。

hMutex = CreateMutex(NULL, FALSE, NULL);//初始的时候,创建的钥匙,要设置为FALSE,就是谁也没有它的拥有权。如果不小心,设置成了TRUE,那么就需要紧跟其后释放一下线程:ReleaseMutex(hMutex);即谁拥有谁释放。

WaitForSingleObject(hMutex, INFINITE);// 某线程,拿到钥匙的使用权。

...// 执行需要进行的内容

ReleaseMutex(hMutex);// 使用完之后,要释放线程的使用权,否则别的线程请求不到,就堵塞了。

#include <Windows.h>// 使用系统API函数,所以需要包含此头文件
#include <iostream>

using namespace std;

int tickets = 100;
HANDLE hMutex; // 两个线程都会用到,所以需要声明为全局变量
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
	while(true)
	{
		WaitForSingleObject(hMutex, INFINITE);// 直到有信号,否则一直等待,不设定超时
		if (tickets > 0)
		{
			Sleep(1);
			cout << "thread1 sell ticket : "  << tickets--<< endl;
		}
		else
		{
			break;
		}
		ReleaseMutex(hMutex);// 释放指定互斥对象的所有权,如果不释放,线程2无法请求
	}
	return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
	while(true)
	{
		WaitForSingleObject(hMutex, INFINITE);
		if (tickets > 0)
		{
			Sleep(1);
			cout << "thread2 sell ticket : "  << tickets--<< endl;
		}
		else
		{
			break;
		}
		ReleaseMutex(hMutex);
	}
	return 0;
}

int main()
{
	HANDLE hThread1, hThread2;
	hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
	hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
	CloseHandle(hThread1);
	CloseHandle(hThread2);

	//一开始为FALSE,表明没有线程拥有这个互斥对象
	hMutex = CreateMutex(NULL, FALSE, NULL);
	Sleep(1000);
	system("pause");
	return 0;
}

运行结果:完美的交替运行。

五、线程同步——创建事件对象CreateEvent

#include <Windows.h>// 使用系统API函数,所以需要包含此头文件
#include <iostream>

using namespace std;

int tickets = 100;
HANDLE hEvent; // 事件对象
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
	while(true)
	{
		WaitForSingleObject(hEvent, INFINITE);// 直到有信号,否则一直等待,不设定超时
		if (tickets > 0)
		{
			Sleep(1);
			cout << "thread1 sell ticket : "  << tickets--<< endl;
		}
		else
		{
			break;
		}
		SetEvent(hEvent);
	}
	return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
	while(true)
	{
		WaitForSingleObject(hEvent, INFINITE);// 请求事件对象,将hEvent设置为非信号状态
		if (tickets > 0)
		{
			Sleep(1);
			cout << "thread2 sell ticket : "  << tickets--<< endl;
		}
		else
		{
			break;
		}
		SetEvent(hEvent);// 将hEvent设置为有信号状态,其他线程可拿去使用
	}
	return 0;
}

int main()
{
    HANDLE hThread1, hThread2;
	hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
	hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
	CloseHandle(hThread1);
	CloseHandle(hThread2);

	// 第二个参数:TRUE,表明人工重置对象, FALSE,非人工,即自动重置对象
	// 第三个参数:FALSE,表明一开始没有线程拥有这个hEvent;无信号状态
	hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	SetEvent(hEvent);// 使得hEvent为有信号状态,其他线程可以取到

	Sleep(1000);
	CloseHandle(hEvent);// 最后需要关闭事件句柄
	system("pause");
	return 0;
}

运行结果:交替进行,直到将票卖完。

六、线程同步——关键代码段

用关键代码段,来控制输出,使得每个线程买一部分票。但是结果:全部都是1线程在卖票,不管是否设置sleep。后来调整Sleep的位置,得到想到的交替进行的结果。

不知道系统内部是如何调用的这个关键代码段标记。跟教科书上不一样。所以最终是不建议用这个。

#include <Windows.h>// 使用系统API函数,所以需要包含此头文件
#include <iostream>

using namespace std;

int tickets = 1000;// 两个线程共同卖票
CRITICAL_SECTION section;// 临界区对象
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
	while(true)
	{
		EnterCriticalSection(&section);// 进入临界区,获取所有权
		if (tickets > 0)
		{
			//Sleep(2);// 不管用
			cout << "thread1 sell ticket : "  << tickets-- << endl;
		}
		else
		{
			break;
		}
		LeaveCriticalSection(&section);// 释放所有权
            Sleep(1);// 会得到正解
	}
	return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
	while(true)
	{
		EnterCriticalSection(&section);
		if (tickets > 0)
		{
			//Sleep(2);// 不管用
			cout << "thread2 sell ticket : "  << tickets-- << endl;
		}
		else
		{
			break;
		}
		LeaveCriticalSection(&section);
            Sleep(1);// 会得到正解
	}
	return 0;
}

int main()
{
	InitializeCriticalSection(&section);// 用之前,必须初始化

	HANDLE hThread1, hThread2;
	hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
	hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
	CloseHandle(hThread1);
	CloseHandle(hThread2);

	Sleep(4000);// 设置的时间一定要等线程运行完,否则,下面的代码会出现问题
	DeleteCriticalSection(&section);// 程序都运行完成之后,释放
	system("pause");
	return 0;
}

如果没有运行完线程,就调用:DeleteCriticalSection(&section); 则会出现如下问题。

正确运行结果:将Sleep(1); 放在关键代码段结束之后。

至此,多线程使用的几种方法整理完毕。至于在多线程中,使用哪个线程,需要根据情况而定。通过这一次的对比,个人还是更倾向于第一种互斥。

发布了417 篇原创文章 · 获赞 156 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/qq_34732729/article/details/105528339