windows下c++ vc2008-- 线程讲解

目录

 

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

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

使用原则:

互斥锁机制:

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

可重入的函数

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


 

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)的函数,又称线程安全(threadsafe)的函数。是指一个函数,在多个线程里同时调用(并发调用)
的时候,其功能仍然正常。

以下函数很可能是不可重入的:
(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资源,要是很长时间查看一次就可能导致仓库装满,放不下了。

方法一:轮询机制

轮询机制的缺点:
查询不能太频繁(浪费cpu),也不能太不频繁(缓冲区满)。难以把握。需要设计一个合理的轮询机制。所以,最好是有一个通知机制:生产者把物品放进去之后,通知到消费者。消费者在接到通知之后,再去取物品。

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

方法二:信号量(推荐)

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

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


OS_Mutex g_mutex;
int g_buf[100]; // 缓冲区:最多存放100个数,相当于公共访问的仓库
int g_count = 0;

OS_Semaphore g_sem(0); //初始化信号量为0

// 第一个线程:生产者,每个一段时间生成一个对象放到 g_buf 中
class Producer : public OS_Thread  
{
public:
	int Routine()
	{
		while(1)
		{
			int r = rand() % 20 + 1; // 生成一个1..20之间的随机数
			OS_Thread::Msleep(50 * r); // 睡觉的间隔50-1000毫秒

			// 存放一个物品(这里就是存一个数, 代表一下物品的意思)
			g_mutex.Lock();
			g_buf[g_count] = r;
			g_count ++;
			printf("放入物品: %d \n", r);
			g_mutex.Unlock();

			g_sem.Post(); // 把信号量的值加1,通知的功能
		}
		return 0;
	}
};

// 第二个线程:消费者
class Consumer : public OS_Thread
{
public:
	int Routine()
	{
		// 轮询机制:频繁查询当前物品的个数
		while(1)
		{
			//OS_Thread::Msleep(800); // 信号量机制不用sleep
			g_sem.Wait(); // 信号量减1 ,看看有没有信号量,有往下执行,没有等待,

			g_mutex.Lock();
			if(g_count > 0)
			{
				for(int i=0; i<g_count; i++)
				{
					printf(" ==== 消费物品: %d \n", g_buf[i]);
				}
				g_count = 0;
			}
			g_mutex.Unlock();
		}
		return 0;
	}
};

int main()
{
	srand(time(NULL));

	// 启动第一个线程
	Producer p;
	p.Run();

	// 启动第二个线程
	Consumer c;
	c.Run();

	// 按回车退出程序
	getchar();
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值