VC++学习之多线程(2)

创建一个线程,自然有一个对应的系统API来完毕。CreateThread这个函数就用来创建线程的。

各种參数的用途我就不多说了,这里直接贴一个我自己练习的样例



1、以下是一个创建一个线程的样例,当然,不过创建;

#include<windows.h>
#include<iostream>
using namespace std;

DWORD WINAPI Fun1Pro(LPVOID laparameter);
int main()
{
	HANDLE hthread1;
	hthread1 = CreateThread(NULL, 0, Fun1Pro, NULL, 0, NULL);
	CloseHandle(hthread1);
	cout << "main thread is running \n";
	return 0;
}

DWORD WINAPI Fun1Pro(LPVOID Laparameter)
{
	printf("The New Thread is running\n");
	return 0;
}
在C++中,对于对象的引用,也是通过引用计数的方式来处理的,所以上述代码中的closehandle这个函数,仅仅是在主线程中关闭了线程的一个句柄。可是实际上创建的这个新线程还是存在的。仅仅只是这个新线程的内核对象中对这个线程的引用计数-1.否则假设不运行这种一个函数,即使这个线程运行完了。也会由于主线程中仍然有引用这个线程的痕迹从而这个线程的引用计数不为0,则不能彻底删除,仅仅有当整个进程计数的时候。才会进行清理。


执行这个程序之后能够发现,并没有看到新线程输出的那句话。

原因是这种。进程启动之后先运行了主线程,由于操作系统是通过分配给线程时间片的方式来让线程运行的,所以。在主线程的时间片内。没有特殊情况是没人打断他的,一直在运行,到了创建新的子线程的时候。由于主线程的时间片没到。所以,不可以运行子线程,之后立即又关闭了子线程的句柄,主线程运行完了,就说明整个进程也就结束了,回收了全部的资源,子线程基本上是无用的。假设要想看见子线程运行的话。那么就须要让主线程暂停运行,这时候。操作系统中的分派器就会在队列里面找到一个新的线程来运行。

让一个线程停止执行的的办法就是利用sleep()函数。使其可以暂停一下。

能够在main函数中return 0;这条语句的前面加上sleep他就 会在主线程结束之前暂停,从而运行新的子线程。


那么主线程和子线程他们直接的执行顺序究竟是什么样的呢?因为线程和进程的执行时间都是又操作系统的时间片来决定的,我对代码做了例如以下改动:

#include<windows.h>
#include<iostream>
using namespace std;

DWORD WINAPI Fun1Pro(LPVOID laparameter);
int index = 0;
int main()
{
	index = 0;
	HANDLE hthread1;
	hthread1 = CreateThread(NULL, 0, Fun1Pro, NULL, 0, NULL);

	CloseHandle(hthread1);
	while(index++<100)
	cout << "main thread is running \n";
	//Sleep(10);
	return 0;
}

DWORD WINAPI Fun1Pro(LPVOID Laparameter)
{
	while(index++<100)
	printf("The New Thread is running\n");
	return 0;
}

执行结果如图:




从图中我们就能够印证上面所说的了,线程的执行时间是靠时间片来决定的,当主线程的时间片执行完,可是整个主线程还没执行完的时候,操作系统的分派器也会将主线程增加线程的就绪队列。从队列中找出新的子进程来执行。



2、利用相互排斥对象实现同步:

相互排斥对象属于内核对象,他可以保证线程对单个资源拥有相互排斥的訪问权,也就是说,仅仅能由一个线程在同一时间訪问该资源。一个相互排斥对象包含:使用数量,线程ID,计数器。

ID表明如今是哪个线程拥有这个相互排斥对象。计数器用于指明该线程拥有相互排斥对象的次数。

须要调用CreateMutex函数来创建相互排斥对象。

通过WaitForSingleObject函数来请求相互排斥对象,通过ReleaseMutex函数来释放相互排斥对象。

可能看到这里大家还是不能明确为什么相互排斥对象能够实现进程间的同步作用。

首先。我们要创建一个相互排斥对象,此时这个相互排斥对象处于一个有信号状态,当我们用wait函数来请求一个相互排斥对象的时候, 那么我们此时的这个线程是可以获得相互排斥对象的訪问权,从而開始运行wait函数后面的共享代码区,同一时候,当调用一次wait的时候,相互排斥对象就从有信号变为无信号状态,由于相互排斥对象就是保证对单个资源的相互排斥訪问,假设此时还有其它的新的线程来请求相互排斥对象的訪问权从而运行共享代码的话。那么此时他得到的相互排斥对象是无信号状态,是不可以继续运行的,此时的线程会处于等待状态。当第一个线程运行完共享代码区的时候。我们要释放如今所使用的这个相互排斥对象,即用releasemutex来进行释放。他会将相互排斥对象从无信号状态设置为有信号状态,此时依据请求的先后顺序,之前在wait函数那里等待相互排斥对象信号的两个线程中的一个,就会被同意获得相互排斥的对象的訪问权,从而运行共享存储区的代码。


事实上上面的相互排斥对象和操作系统中的PV操作是同理的。

共享代码就是在wait函数和release函数中间的代码。每次仅仅能同意一个线程获得相互排斥对象的訪问权从而进入到临界区里面。wait是请求操作,release是恢复操作。


相互排斥对象另一个重要的机制,就是他包含的线程的ID.假设是线程1请求了一个相互排斥对象。那么假设线程二想用这个相互排斥对象之前,必须由线程1进行释放,在释放的过程中,相互排斥对象会拿出他维护的线程ID和释放它的线程进行比較,仅仅有两者同样的情况下才可以释放这个相互排斥对象。否则假设线程ID和释放者不一样 ,则不可以释放相互排斥对象。


CreateMutex函数中的第二个參数有两个取值,一个是false,一个是true.false代表当前的线程不拥有这个相互排斥对象,相互排斥对象处于有信号状态。true呢表示当前的线程拥有这个相互排斥对象,即当前的线程和该相互排斥对象绑定,相互排斥对象处于无信号状态。相当于默认的运行了wait函数。假设此时在这个线程里面再调用wait函数,尽管此时的相互排斥对象是无信号状态,可是由于调用他的线程和相互排斥对象内部维护的线程ID是相等的,所以依旧会获得该相互排斥对象的控制权。可是此时有一个很重要的一点,就是这个相互排斥对象的计数器+1了。一開始就说过。相互排斥对象的计数器代表了拥有这个相互排斥对象的线程拥有它的次数,所以每当调用一次wait函数的时候。计数器都会+1.所以。假设说当前线程不须要这个相互排斥对象的时候,须要调用两次释放函数,计数器才会减为0,这也就告诉了我们。释放函数的功能实际上就是让计数器-1.


3、关于相互排斥对象另一个非常重要的功能:

当一个线程里面利用wait函数请求相互排斥对象运行完成之后,在线程中并没有调用release函数来释放这个对象。所以在线程结束之后操作系统假设发现线程已经终止的话。他会自己主动帮我们释放掉这个相互排斥对象。把它变为有信号状态。




4、怎样保证仅仅有一个实例执行:

我们都知道程序仅仅是代码,一个程序执行起来能够有多个实例,那么假设仅仅让这个程序仅仅有一个实例在执行呢。

解决的方法就是利用命名的相互排斥对象来实现。原因就是。假设在调用创建相互排斥对象的函数的时候。假设之前已经有该命名的相互排斥对象存在,那么就返回已经创建的相互排斥对象,不再创建 新的,这时getlasterror将返回error_alreday_exists。

所以,假设createmutex返回的是一个有效句柄,接下来就要推断getlasterror的返回值是什么,从而断定我们 是不是在先前已经创建过了一个相互排斥对象,也就相当于已经有一个实例正在执行。


posted @ 2017-04-21 12:28  llguanli  阅读(276)  评论(0编辑  收藏  举报