⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 线程的同步与互斥.txt

📁 操作系统方面关于线程的代码。包括一个公共变量的多线程减问题;临界资源不加锁的多线程程序;有加锁机制的多线程编程等。
💻 TXT
字号:
实验五要求

实验四的内容,估计大家应该是有很多内容是看不明白的。
*********************************************
*   本实验涉及程序,均可在命令提示符下使用:*
*	CSC 程序名称                        *
*   编译并执行。                            *
*********************************************

1 一个公共变量的多线程减问题,程序如下:文件名称:Join线程\Th1.cs
namespace th1
{
	class ThTest1
	{
	public int N=20;
		public void M1()
		{
			int i;
			for(i=0;i<10;i++)
			{
				N--;
				Console.WriteLine("In M1:N="+N);
			}
		}
		public void M2()
		{
			int i;
			for(i=0;i<10;i++)
			{
				N--;
				Console.WriteLine("In M2:N="+N);
			}
		}
		static void Main(string[] args)
		{
		ThTest1 P=new ThTest1();
		ThreadStart ts1=new ThreadStart(P.M1);
		ThreadStart ts2=new ThreadStart (P.M2);
		Thread t1=new Thread(ts1);
		Thread t2=new Thread(ts2);
		t1.Start();
		t2.Start(); 
		}
	}
}
这个程序中,N假设是我们程序中的临界资源,这个程序中没有对线程、公共变量N做任何控制,执行这个程序,
结果如下:
In M2:N=18
In M2:N=17
In M1:N=19
In M1:N=16
In M1:N=15
In M1:N=14
In M1:N=13
In M1:N=12
In M1:N=11
In M1:N=10
In M1:N=9
In M1:N=8
In M2:N=7
In M2:N=6
In M2:N=5
In M2:N=4
In M2:N=3
In M2:N=2
In M2:N=1
In M2:N=0

我们不难发现:在线程M2中,10次循环减N未完成,则已经有M1线程插入,或一开始就执行M1线程,但同样在10次
递减未完成时,就已经插入M1线程。
事实上,可能每一次运行这个程序,结果都是不同的,而且不同的计算机上运行,结果也不一致。
我们希望的结果是这样的:
In M1:N=19
In M1:N=18
In M1:N=17
In M1:N=16
In M1:N=15
In M1:N=14
In M1:N=13
In M1:N=12
In M1:N=11
In M1:N=10
In M2:N=9
In M2:N=8
In M2:N=7
In M2:N=6
In M2:N=5
In M2:N=4
In M2:N=3
In M2:N=2
In M2:N=1
In M2:N=0
就是说:由一个线程完成10次递减工作后,再有另一个线程继续做递减。我们需要一个每次都一致的、
有序的准确结果,而不是每次都不一样的结果。

解决上述问题,就是所谓的线程同步问题,我们必须强制在一个时间内仅仅只有一个线程来工作。解决这个问题的方法
之一就是进程Join

2 Join线程
程序如下:文件名称:Join线程\Th2.cs
using System;
using System.Threading;
namespace th2
{
	class ThTest2
	{
		public int N=20;
		public void M1()
		{
			int i;
			for(i=0;i<10;i++)
			{
				N--;
				Console.WriteLine("In M1:N="+N);
			}
		}
		public void M2()
		{
			int i;
			for(i=0;i<10;i++)
			{
				N--;
				Console.WriteLine("In M2:N="+N);
			}
		}
		static void Main(string[] args)
		{
			ThTest2 P=new ThTest2();
			ThreadStart ts1=new ThreadStart(P.M1);
			ThreadStart ts2=new ThreadStart (P.M2);
			Thread t1=new Thread(ts1);
			Thread t2=new Thread(ts2);
			t1.Start();
			t1.Join();      //这里是最关键的语句:合并两个线程。同Th1.cs比较,仅仅这里加了一句;
			t2.Start(); 
		}
	}
}

合并t1、t2两个线程,强制使两个线程的执行次序成为一个有序的队列,这样,实际就相当于仅有一个线程、在
特定的时刻访问临界资源N。
这个程序的结果就是我们所需要的了。

请修改上述程序Th1.cs,使用教材P83、解法1:只能有一个线程递减N。事实上,该方法在单CPU计算机上有很有效的。
在没有提供诸如Join()这类方法的时候,老程序员们都这么做。

在C#中,另一个更有效的方法是临界资源加锁,再看一个程序,这也是一个没做资源、线程控制的例子:

3 临界资源不加锁的多线程程序
本程序启动了两个线程:t1、t2,每个线程所做的处理仅仅为:x++;y++;
由于x、y未加锁,所以多线程处理后,这两个变量中的结果完全可能是不同的。
注意:该程序请用Ctrl-C来结束
程序文件:Lock临界资源\Th3.cs

using System;
using System.Threading;
namespace th3
{
	class ThTest3
	{
		static void Main(string[] args)
		{
			MyData num=new MyData();  //说明num是一个MyData类型的对象 
			Thread t1=new Thread(new ThreadStart(num.Run)); //建立两个线程,共同处理Run()
			Thread t2=new Thread(new ThreadStart(num.Run)); //两个线程使用同一个对象num,所以也是同一个x,y 
			t1.Start();
			t2.Start();              //启动两个线程,运行一个程序函数Run(),加x,y
			for (int i=0;i<10;i++)   //鉴于Run()实际是死循环,所以主线程Main等它运行10秒吧!
			{
				Thread.Sleep(1000);
				num.TestEquals();  //对比x,y并显示结果
			}
		}
	}

	class MyData
	{
		private int x=0,y=0;
		public void Inc()
		{
			x++;y++;
		}

		public void TestEquals()
		{
			Console.WriteLine (x+","+y+":"+(x==y));//类似C中:printf("%d,%d:%s\n",x,y,(x==y)?"True":"False);
		}

		public void Run()
		{
			while(true)
				Inc();
		}
	}
}

注意:这个程序中有两个类:ThTest3、MyData。
类MyData中的Inc()方法很简单,加x、y,而Run()则就有点不厚道:死循环加啊,当然,这是为了让大家看明白结果的
愚蠢做法,实际编程中请一定不要这样。

先分析Inc():
		public void Inc()
		{
			x++;y++;
		}
从程序中可以看出:x,y是一个线程里“同时”被加的变量。从一般意义上理解:
Main()中的t1、t2线程中运算,x,y应该是相等的!也就是说,在最后调用TestEquale()时,结果都应该是:真!
但实际运行这个程序,10秒内肯定会有False的结果出现!

为什么?很简单:仅仅在t1这个线程内、仅仅做x++;y++这样的最简单操作,由于时间片的循环,当只做完x++的时候,
y++还没来得及做,已经被t2线程所截断!结果肯定是x、y的结果不同了!
这就是所谓抢断式分时操作系统的特性!
这是我的一组结果:当然,你们的或许和这个也并不一致,但有False就算。
66759156,66759156:True
148574656,148574656:True
216236716,216236716:True
283601280,283601280:True
351035547,351035547:True
418685630,418685630:True
480403593,480403592:False
537051104,537051103:False
593892308,593892308:True
651068184,651068184:True
^C

什么意思:这个程序没有同步加x、y,这是很恐怖的结果!很多情况下,我们需要处理类似这样的工作:扣你的钱、再
加到我的名字下面(想的很美吧?),这种工作必须是不可分割的!不允许有上述这种情况发生。

解决这个问题的方法就是加锁。

4 有加锁机制的多线程编程
程序文件:Lock临界资源\Th4.cs
基于3的问题,我们首先要对x、y变量加锁,看看下面的程序,加锁是很简单的事情

using System;
using System.Threading;
namespace th4
{
	class ThTest4
	{
		static void Main(string[] args)
		{
			MyData num=new MyData();
			Thread t1=new Thread(new ThreadStart(num.Run));
			Thread t2=new Thread(new ThreadStart(num.Run));
			t1.Start();
			t2.Start();
			for (int i=0;i<10;i++)
			{
				Thread.Sleep(1000);
				num.TestEquals();
			}
		}
	}

	class MyData
	{
		private int x=0,y=0;
		public void Inc()
		{
			lock(this)              //加锁,就这点学问,不过请看明白this是什么意思!
			{
				x++;y++;
			}
		}

		public void TestEquals()
		{
			Console.WriteLine (x+","+y+":"+(x==y));
		}

		public void Run()
		{
			while(true)
				Inc();
		}
	}
}

再编译运行这个程序吧,肯定没False了,再有砸MS去啊!
数据库经常用这个技术,分的更细致:表、记录加锁都是这个意思。
加锁的含义就是:仅仅允许一个进程访问这个临界资源!

请总结3、4,别忘了这些手艺即可。

最后,请分析在什么情况下使用Join()方法,什么时候使用Lock比较好。


5 多线程求PI
看了1、2、3、4,估计有这样的想法:这么处理线程,线程都互斥了,还有什么意义啊?
这要根据实际问题分析:实际中有的问题,临界资源是互斥的,有的也不一定,例如以下这个例子:

		Pi/4=1-1/3+1/5-1/5.......

我们期望一个线程计算前2000项,一个线程计算后2000项,结果加在一个变量里面,这个问题中,求和变量就不是临界资源,
两个线程就不必互斥,如果互斥,那结果就麻烦了!

所以说:是否需要同步线程,首先要看是否有临界资源!

但实际情况中,一个程序如果没有临界资源,线程就不必同步了,下面的这个程序,其函数S1()、S2()中使用了变量InS1、
InS2,并且在Main()中使用了非常关键的一段:
		while(P.InS1==1 || P.InS2==1)
			;
请分析这段语句的作用吧!看看注释后是否还能显示结果?
这一招大概是操作系统教材中没有的,但很多老手们都知道这种做法,简单而且实用。请总结这种做法适用的条件。

using System;
using System.Threading;
namespace PIC
{
	class MyClass
	{
		public double PI;
		public short InS1=0,InS2=0;
		public void GetPI(int n,int m)
		{
		int i;
		double Sign=1;
		for(i=n;i<m;i++)
			{
			PI=PI+Sign/(2.0*(double )i+1.0);
			Sign=-Sign;
			}
		}
		public void S1()
		{	
			InS1=1;
			GetPI(0,100000);			
			InS1=0;
		}
		public void S2()
		{	
			InS2=1;
			GetPI(100001,200000);			
			InS2=0;
		}

		public static void Main(string[] args)
		{
		MyClass P=new MyClass();
		P.PI=0;
		ThreadStart ts1=new ThreadStart(P.S1);
		ThreadStart ts2=new ThreadStart(P.S2);
		Thread t1=new Thread(ts1);
		Thread t2=new Thread(ts2);
		t1.Start();
		t2.Start();
		while(P.InS1==1 || P.InS2==1)
			;
		Console.WriteLine("PI="+P.PI*4);
		}
	}
}



































⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -