【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
多线程编程一般都会涉及到锁的时候,很多人可能觉得很意外,为什么会需要这么一个锁。本质上,这主要还是因为多线程的执行中,本身一部分逻辑并不是原子操作导致的。有一个池塘喂鱼的例子最为经典。假设池塘有两个人同时去喂鱼,每一个人喂鱼之前,会先看一下池塘边上的牌子。假设牌子是红色的,代表已经喂过了;假设牌子是绿色的,则代表鱼还没有喂过。鱼本身只能吃一顿,如果连续喂的话,那么鱼可能会撑死。
现在就会出现这么一个情况,就是第一个人去喂鱼,但是他还没有来得及翻牌子的时候,第二个人来继续喂鱼。他一看鱼牌子是绿色的,还没有喂,那就就会选择继续投料。而他投料的同时,并不知道第一个人之前已经投喂过了。所以,这个时候,鱼就会被撑死了。
所以,为了解决这个问题,os一般会提供一个锁的机制,对于锁里面的操作,一定是不能打断的。只有所有操作都完成之后,才会释放自己的锁机制。为了解释锁是怎么使用的,以及说明如果不用锁的话,究竟有什么样的坏处,可以通过c# wpf编写一个demo进行说明下。
1、设置界面
界面还是只有一个按钮和一个textbox。按钮下去的时候,有两个thread同时递增1000万次,查看两个thread递增之后,总的数据次数是不是2000万。
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:WpfApp"
mc:Ignorable="d"
Title="LockSimulationDemo" Height="480" Width="550">
<Grid>
<StackPanel>
<Button x:Name="button" Content="Start processing" Click="StartButton_Click" Height="40" Margin="5,40"/>
<TextBox x:Name="Result" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Height="200" Margin="5"/>
</StackPanel>
</Grid>
</Window>
相关界面显示如下,
2、代码编写
代码编写主要就是按钮的回调函数。回调函数中,主要使用了Thread类、ThreadStart类这两个。线程注册函数是WorkerThreadMethod。创建好两个thread之后,就可以将他们start开始执行。
在线程注册函数中,会各循环1000万次。之所以会循环这么多次,是因为循环次数多了,才能看到锁的效果。没有锁的话,最终的累加次数不一定是2000万;反之,有了锁,肯定是2000万,这就是锁的用处所在。
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Threading;
namespace WpfApp
{
public partial class MainWindow : Window
{
private int total = 0;
private readonly object _lockObject = new object(); // lock for synchronization
// construct function
public MainWindow()
{
InitializeComponent();
}
// button invoke function
private void StartButton_Click(object sender, RoutedEventArgs e)
{
Thread newThread1 = new Thread(new ThreadStart(WorkerThreadMethod));
Thread newThread2 = new Thread(new ThreadStart(WorkerThreadMethod));
Result.Text = "";
total = 0;
button.IsEnabled = false;
newThread1.Start();
newThread2.Start();
}
// thread entry function
private void WorkerThreadMethod()
{
for (int i = 0; i < 10000000; i++)
{
lock (_lockObject) // critical section
{
total += 1;
}
}
Application.Current.Dispatcher.Invoke(() =>
{
Result.AppendText(total.ToString() + "\n");
button.IsEnabled = true;
});
}
}
}
3、实验和验证
验证的话,编译没有啥问题,直接单击按钮即可。同时,这个按钮是可以连续单击,即一次结果出来之后可以反复测试的。中间测试的过程中,可以通过注释掉lock代码的方式,判断注释前后运行结果有没有差异。