1.线程间共享数据
各线程间可访问全局变量
互斥锁:协调线程,使其步调一致(Mutex)。
g_mutex.lock()//上锁,g_mutex.unlock();//解锁
锁资源由操作系统定义。
互斥锁的使用原则:减少占用时间(尽量缩短对共享数据的访问时间)。
实例:互斥锁
#include <stdio.h>
#include "osapi/osapi.h"
char g_key[16]; // Generator更新它,Checker获取它
class KeyGenerator : public OS_Thread
{
private:
virtual int Routine()
{
int times = 0;
while(1)
{
// 更新key
for(int i=0; i<16; i++)
{
g_key[i] = times;
}
times ++;
if(times >= 128) times = 0;
OS_Thread::Msleep(50);
}
return 0;
}
};
class KeyChecker : public OS_Thread
{
private:
// 线程主函数
virtual int Routine()
{
while(1)
{
// 检查完整性
for(int i=1; i<16; i++)
{
if(g_key[i] != g_key[i-1])
{
printf("g_key[%d]=%02X,g_key[%d]=%02X\n",
i,g_key[i],i-1,g_key[i-1]);
PrintKey();
printf("不完整!!\n");
return 0;
}
}
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;
}
检查线程机制,发现出现错误,出现了读写不一致。
应用锁机制:
#include <stdio.h>
#include "osapi/osapi.h"
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();
}
}
***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;
}
上述锁机制还可以继续改进:因为拷贝的时间相对较短,因此可以替拷贝加锁,然后解锁。具体代码如下:
#include<stdio.h>
#include "osapi/osapi.h"
OS_Mutex g_mutex;//锁机制使用时间不能太长
char g_key[16];
class KeyGenerator :public OS_Thread
{
private:
virtual int Routine(){
int times = 0;
while(1)
{
// 先生成key: 需要80ms
char key_new[16];
for(int i=0; i<16; i++)
{
OS_Thread::Msleep(5);
key_new[i] = times;
}
// 更新key: 占有锁的时间非常短
g_mutex.Lock();
memcpy(g_key, key_new, 16);
g_mutex.Unlock();
times ++;
if(times >= 128) times = 0;
printf("写!");
}
return 0;
}
};
class KeyChecker : public OS_Thread
{
private:
// 线程主函数
virtual int Routine()
{
while(1)
{
// 尽量缩短对共享数据的访问时间
char copy[16];
g_mutex.Lock();
memcpy(copy, g_key, 16);
g_mutex.Unlock();
// 数据处理
// 检查完整性
for(int i=1; i<16; i++)
{
if(copy[i] != copy[i-1])
{
printf("不完整!!\n");
PrintKey();
//return 0;
}
} printf("读!\n");
OS_Thread::Msleep(50);//必须加上Sleep函数,否则无法实现调度
}
return 0; // 正常退出
}
void PrintKey()
{
printf("Key: ");
for(int i=0; i<16; i++)
printf("%02X ", g_key[i]);
printf("\n");
}
};
int main(){
KeyGenerator mytask1;
mytask1.Run();
KeyChecker mytask2;
mytask1.Run();
getchar();
return 0;
}
主要就是:
更新时上锁:
g_mutex.Lock();
memcpy(g_key,key_new,16);//将生成的16个放入g_key中。
g_mutex.UnLock();
读时上锁:
g_mutex.Lock();
mecpy(copy,g_key,16);//考入copy数组中
g_mutex.UnLock();
2.线程安全函数—-可重入函数
可重入函数:指一个函数,在多个线程里同时调用(并发调用)的时候,其功能任然正常。(线程安全函数)
定义全局变量 int result;
#include <stdio.h>
#include "osapi/osapi.h"
int result;
int sum(int n){
result=0;
for (int i=1;i<=n;i++)
{
result+=i;
}
return result;
}
class MyTask:public OS_Thread
{
private:
virtual int Routine()
{
while (1)
{
int ret=sum(100);
if (ret !=5050)
{
printf("%d\n",ret);
}
OS_Thread::Msleep(50);
}
return 0;
}
};
int main(){
MyTask a;
a.Run();//启动写线程
MyTask b;
b.Run();
getchar();
return 0;
}
//出现错误
即全局变量用作为函数参数容易出现错误。
单线程、多线程都可重入时即为可重入函数。
可用互斥锁实现,但最好不要用,耗用系统资源。
3.线程间的通知机制—-信号量机制
生产者–消费者模式:生产者不断往缓存中写东西,消费者不断取走东西。需要保证能够及时取走。
保证能够及时取走的机制:(1)轮询机制(2)信号量机制
(1)轮询机制
#include <stdio.h>
#include "osapi/osapi.h"
#include <time.h>
#include <stdlib.h>
int g_buf[100];//缓存区
OS_Mutex g_mutex;//互斥锁
int g_count=0;
class Producer:public OS_Thread{
public:
virtual int Routine(){
while (1)
{
int n=rand()%20+1;//生成一个1....20之间的数
OS_Thread::Msleep(50*n);//50到1000毫秒
g_mutex.Lock();//上锁
//存放一个数字代表一个物品
g_buf[g_count]=n;
g_count++;
printf("放入的物品为:%d\n",n);
g_mutex.Unlock();//解锁
}
return 0;
}
};
class Consumer:public OS_Thread
{
public:
virtual int Routine(){
while (1)
{
OS_Thread::Msleep(500);//每800毫秒取一次
g_mutex.Lock();//上锁
if (g_count>0)//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;
}
轮询机制的缺点:
1)轮询机制的时间间隔不宜太长,否则缓存区会满;
2)轮询机制的时间不宜太短,否则会百等
(2)信号量机制(semaphore)
及时取,生产一个,消费一个,协调生产和消费。
post()
wait():当信号量大于1时,减1,为0时,自动阻塞
//信号量机制不用加sleep,在消费者中,加sleep代表轮询机制。
具体实现:
#include "osapi/osapi.h"
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
OS_Mutex g_mutex;//锁机制
int g_buf[100];//缓冲区,100个数;
int g_count =0;
OS_Semaphore g_sem(0);//信号量的值初始化为0
// 第一个线程:生产者
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();
}
return 0;
}
};
// 第二个线程:消费者
class Consumer : public OS_Thread
{
public:
int Routine()
{
// 轮询机制:频繁查询当前物品的个数
while(1)
{
//OS_Thread::Msleep(800);
g_sem.Wait();//检测信号量是否有值,信号量机制自己阻塞;(判断轮询与信号量的方法)
//wait()重载了超时等待函数,加参数,可以进行超时处理
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;
}
相比于轮询机制,加了post(),wait(),其余在信号量的消费者中不能用Msleep,因为wait有阻塞机制。post的初始值随初始值而定。
信号量机制:只是相对及时。
4.OSAPI的使用
windows api也可以实现线程编程:
#include <stdio.h>
#include<process.h>
#include <windows.h>
#include <string.h>
//使用windows api实现线程
unsigned int WINAPI OS_Thread(void *param){
//定义回调函数,必须加上WinAPI
for (int i=0;i<10;i++)
{
printf("do my job:%d\n",i);
Sleep(1000);
}
return 0;
}
unsigned int WINAPI OS_Thread1(void *param){
//定义回调函数,必须加上WinAPI
for (int i=0;i<10;i++)
{
printf("do your job:%d\n",i);
Sleep(500);
}
return 0;
}
int main(){
unsigned int thraddr;
HANDLE handle=(HANDLE) _beginthreadex(NULL,
0,
OS_Thread,
//回调函数(按照MSDN的定义来实现线程定义)
NULL,0,&thraddr);
HANDLE handle1=(HANDLE) _beginthreadex(NULL,
0,
OS_Thread1,
//回调函数(按照MSDN的定义来实现线程定义)
NULL,0,&thraddr);
getchar();
return 0;
}
但这种方法不可取,很麻烦,可以直接将其封装后,再使用。