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

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

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

线程创建函数CreateThread

修改说明:

CreateThread(); 来创建线程其实是一种不太好的方法,在实际使用中尽量使用_beginthread()来创建线程,因为更加的安全。

函数的原型:

HANDLE WINAPI CreateThread(
  _In_opt_  LPSECURITY_ATTRIBUTES  lpThreadAttributes,  
  _In_      SIZE_T                 dwStackSize,
  _In_      LPTHREAD_START_ROUTINE lpStartAddress,
  _In_opt_  LPVOID                 lpParameter,
  _In_      DWORD                  dwCreationFlags,
  _Out_opt_ LPDWORD                lpThreadId
);

参数说明:

第一个参数 lpThreadAttributes 表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。

第二个参数 dwStackSize 表示线程栈空间大小。传入0表示使用默认大小(1MB)。

第三个参数 lpStartAddress 表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。

第四个参数 lpParameter 是传给线程函数的参数。

第五个参数 dwCreationFlags 指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂停运行,这样它就无法调度,直到调用ResumeThread()。

第六个参数 lpThreadId 将返回线程的ID号,传入NULL表示不需要返回该线程ID号。

返回值

CreateThread的返回值是线程的句柄,失败的话就返回NULL。

用完线程需要关闭线程句柄。CloseHandle(hThread);

一、最简单的多线程原型

#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); 放在关键代码段结束之后。

 

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值