windows下c++ vc2008 线程讲解

OSAPI设计用于跨平台编程这套代码既适合于Windows,也适合于Linux为什么呢?其实吧,这两个实现方式是不一样的,但是实现了一个更高级的抽象的调用方式,这个方式不管是哪个系统下的,调用方式是一样的,使用条件...

 

 

OSAPI设计用于跨平台编程
这套代码既适合于Windows,也适合于Linux

为什么呢?其实吧,这两个实现方式是不一样的,但是实现了一个更高级的抽象的调用方式,这个方式不管是哪个系统下的,调用方式是一样的,

使用条件编译可以实现同时在两种系统下都可以使用

Linux实现的线程是这样的,第一条语句定义了如果没有不是在win32上则失效了,同理windows也是这样实现的

#ifndef _WIN32

可以不用这个osapi,对于windows系统调用系统的api也行,但是不方便

 

简单实现多线程:这里是创建两个线程和一个主线程以及join等待其他线程

1、#include "osapi/osapi.h" #这个文件由阿发提供,没有人提供我这么办?

#define _CRT_SECURE_NO_WARNINGS /* VS2013,2015需要这一行 */
#include <stdio.h>

#include "osapi/osapi.h" //这个文件由阿发提供,

// 定义一个类
class Buddhist : public OS_Thread //这个类存在于osapi.h中,但是其实没有,在Thread.h中,进一步include
{
private:
	virtual int Routine() //重写了这个Routine函数,并且是虚函数,具有多态的功能
	{
		// 线程体: 执行它的任务
		for(int i=0; i<100; i++)
		{
			printf("ma mi ma mi hong ...\n");
			OS_Thread::Sleep(1);
		}
		return 0;
	}
};

class Confucian : public OS_Thread
{
private:
	virtual int Routine() //这个函数数线程的主函数,入口函数
	{
		for(int i=0; i<500; i++)
		{
			printf("人之初,性本善 ...\n");
			OS_Thread::Sleep(1);
		}
		return 0;
	}
};
int main()
{
	Buddhist  task1; // ?这只是创建一个C++里的对象
	task1.Run(); //创建并启动线程,这个是操作系统完成的。Run()的内部告诉OS来创建一个线程

	Confucian  task2;
	task2.Run();
	//OS_Thread::Join(&task1); //等待task1退出,才能执行主线程。 Join是OS_Thread类的一个虚函数

	// 
	printf("--------- 主线程开始 -------\n");
	for(int i=0; i<10; i++)
	{
		printf("********* \n");
		OS_Thread::Sleep(1);
	}

	getchar();

	return 0; //正常退出线程
}

 

多个线程访问全局变量时候,实现互斥锁,--不能中断对数据的操作

互斥锁,C++里一般称为Mutex, Java里则一般称为Lock

1. 多个线程之间,可以通过全局对象/堆对象来共享数据
2. 当访问共享数据时(有读有写),为了保证数据的完整性,需要使用互斥锁机制
3. 一个使用原则:尽量缩短对lock的占有时间

使用原则:

当一个线程占有锁时,应该尽快地完成对共享数据的访问。因为别的线程还在等待这个锁呢。

互斥锁机制:


“在一个线程获取锁(Locked)之后,另一个线程的Lock操作会一直等待(阻塞),直到该锁被释放(Unlocked)”

#define _CRT_SECURE_NO_WARNINGS /* VS2013,2015需要这一行 */
#include <stdio.h>

#include "osapi/osapi.h"

/*
此程序:问题:一个全局变量,g_key,KeyGenerator这个线程可以改写他的内容,
当写着写着,发生一个可怕的事情,还没写完呢!就切换到另一个线程了。
这个程序告诉我,怎样防止这个事情
	//很有可能在i=2的时候就断了,不是我们想要的我希望i从0到16,不能被中断

*/
//互斥锁,C++里一般称为Mutex, Java里则一般称为Lock
OS_Mutex g_mutex; //创建一把锁,这是一个操作系统的方法,自己写不了
char g_key[16]; // Generator更新它,Checker获取它。这是一个全局数据,多个线程可以访问他

class KeyGenerator : public OS_Thread
{
private:
	virtual int Routine()
	{
		int times = 0;
		while(1)
		{
			// 更新key
			g_mutex.Lock(); //上锁
			for(int i=0; i<16; i++)
			{
				OS_Thread::Msleep(5);
				g_key[i] = times;
			}
			g_mutex.Unlock();//解锁

			times ++;
			if(times >= 128) times = 0;
			//OS_Thread::Msleep(50);
		}
		return 0; 
	}
};

class KeyChecker : public OS_Thread
{
private:
	// 线程主函数
	virtual int Routine()
	{
		while(1)
		{		
			// 数据处理
			// 检查完整性
			g_mutex.Lock();
			for(int i=1; i<16; i++)
			{
				if(g_key[i] != g_key[i-1])
				{
					printf("不完整!!\n");
					PrintKey();
					//return 0;
				}
			}
			g_mutex.Unlock();

			//OS_Thread::Msleep(50);
		}
		return 0; // 正常退出
	}

	void PrintKey()
	{
		printf("Key: ");
		for(int i=0; i<16; i++)
			printf("%02X ", g_key[i]);					
		printf("\n");
	}
};


int main()
{
	KeyGenerator a;
	a.Run();

	KeyChecker b;
	b.Run();

	getchar();


	return 0;
}

 

线程安全--多个线程调用同一个函数时候可能引发这个函数功能失常,

可重入的函数

可重入(reentrant)的函数,又称线程安全(thread
safe)的函数。
是指一个函数,在多个线程里同时调用(并发调用)
的时候,其功能仍然正常。

 

以下函数很可能是不可重入的:
(1)一个全局函数(写在类体之外的函数)
如果它借助于全局对象来实现,并且有写操作,那
么就是不可重入的。
(2) 一个类的成员函数
它访问并修改了成员变量,那么一般情况下它就是
不可重入的。

如何将不可重入的函数,改为可重入的?
(1) 不借助外部的变量来实现
尽量用本函数内定义的局部变量来实现。
或者在本函数动态创建对象、并在退出前销毁对象
(没有外部依赖,不操作外部变量)
(2) 实在不行的话,加上互斥锁控制
(回到上一讲)

#define _CRT_SECURE_NO_WARNINGS /* VS2013,2015需要这一行 */
#include <stdio.h>
#include "osapi/osapi.h"

int result;
OS_Mutex g_mutex;

// 求和: 1 + 2 + ... + n
int sum(int n) //这个是线程安全的,运行没有任何东西则是安全的
{
	g_mutex.Lock();
	result = 0; //假设这个是全局变量则变成,线程不安全了,不可重入了,解决方法使用互斥锁
	for(int i=1; i<=n; i++)
	{
		result += i;
	}
	int r = result;
	g_mutex.Unlock();
	return r;
}

class MyTask : public OS_Thread
{
public:
	virtual int Routine()
	{
		while(1)
		{
			int ret = sum(100);
			if(ret != 5050) 
				printf("%d \n", ret);

			OS_Thread::Msleep(5);
		}
		return 0;
	}
};

int main()
{
	MyTask t;
	t.Run();

	MyTask t2;
	t2.Run();

	getchar();
	return 0;
}

线程间的通知机制 - 信号量

 

轮询机制的缺点:
查询不能太频繁(浪费cpu),也不能太不频繁
(缓冲区满)。难以把握。
需要设计一个合理的轮询机制

 

所以,最好是有一个通知机制:生产者把物品放进
去之后,通知到消费者。消费者在接到通知之后,
再去取物品。

 

OS_Semaphore g_sem;
第一个线程: Producer
g_sem.Post(); // 通知
第二个线程:Consumer
g_sem.Wait(); // 等待通知

 

(2) 用信号量实现线程间的通知机制
线程在等待信号量的时候,它是不占cpu的,相当于
被阻塞的状态。

 

本文标题为:windows下c++ vc2008 线程讲解

基础教程推荐