很难的问题:关于 多线程任务排队,我搞不掂啊,大侠来看看(内有详细描述)

老龙友 2008-07-23 02:04:52
情况是这样的,我用VB写一个样品交接程序。
用户用一个条码扫描器扫描某个条码,扫描之后,系统做一系列判断,如果正常,则把该条码所对应的Job添加到页面的 datagridview中

现在的问题是,由于这个“一系列判断”实在太复杂了,要访问和更新多个表,插入多个数据,耗时很长,以至于扫描了条码之后,需要大约1-2秒才能添加到datagridview表,这对于用户来说是不可接受的,因为他们扫描的速度非常快,等待扫下一个条码的时间不能超过0.1秒。

这样,我想到了一个办法,用多线程的方式来实现,也就是当用户扫描条码以后,先进行最关键的判断,如果没有问题,那么把该Job添加到datagridview表,复杂的操作全部交给一个单独的线程到后台去运行,而界面可以马上等待下一个条码输入。

大概的形式是这样的,当然实际的程序非常长,我给精简成下列形式

Public Class frmHandover()

Private thdHO as Threading.Thread

Private Sub Handover()
Dim strJobNo as String = tbxJobNo.Text
Dim tmpHO As New clsHO
'将clsHO 实例化
tmpHO.strJobNo = strJobNo

'定义一个线程变量
thdHO = New Threading.Thread(AddressOf tmpHO.HOitem)
thdHO.Start()
End Sub

End Class


Public Class clsHO

Public strJobNo as String

Public Sub HOitem()
'.....此处省略一系列复杂操作
da.UpdateCommand = New OleDb.OleDbCommand(strSQL, conn)
da.UpdateCommand.ExecuteNonQuery()
'经常在这里出现死锁
End Sub

End Class

这样,速度慢的问题确实解决了,每次扫描之后到界面可以准备好接受下一个扫描的时间不超过0.1秒。
但是,又产生了新的问题:在 da.Updatecommand.ExecuteNoQuery() 这个地方,经常会出现提示:

“事务(ID XXX)与另一个进程被死锁在 Lock 资源上,并且已被选作死锁牺牲品,请重新运行该事务。”

我Baidu了好多地方,分析得出结论,这还是因为扫描速度过快,上一个Thread的任务还没处理完,下一个Thread已经开始了,而两个Thread都试图更新同一个表,就可能造成这个问题。

所以我现在的感觉是,当扫得很快的时候,有多个Thread在系统中,他们同时操作数据库造成了这个问题。
当然,我也不能确定是否是因为多个人同时扫描造成的这个问题。但这个死锁问题确实是在使用了多线程技术之后才出现的,所以我暂时认为是多个Thread而不是多个人造成的死锁。

现在应该怎么解决这个问题呢?我冥思苦想了很久,觉得应该去控制线程,应该让这个线程在处理完一个任务之后,再去处理第二个任务,而不是同时处理几个任务。

但是怎样才能实现这一点呢?因为我对线程不是很熟,因此请各位大虾指点。



另外,如果确实是由于多人操作造成了死锁,那么怎样才能解决这个问题呢?是否要在数据库上做什么设置?比如调整阀值之类的?
因小弟很少上CSDN,所以没什么分,也不知道怎么弄,只能恳请各位不吝赐教了,多谢,多谢!
...全文
377 21 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
21 条回复
切换为时间正序
请发表友善的回复…
发表回复
ViewStates 2008-07-23
  • 打赏
  • 举报
回复
[Quote=引用 19 楼 andywongz 的回复:]
引用 17 楼 atlasroben 的回复:
我说下我的思路你的问题可以简单化
线程池并发操作,当到了写入数据库的那一步的时候,将一个公共变量lock,当你数据写完以后就unlock,其他的线程在执行到写入数据库的代码的时候都会尝试lock那个公共变量,如果已经被lock了就会等待,直到unlock为止


非常感谢!这个思路确实可以解决我的问题!
但我还有一个小问题,你也看到了,我的程序是每次运行这个子程序都会创建一个新的线程,我觉得…
[/Quote]

波浪线就是其他线程执行的地方了。多线程很难调试。。。
老龙友 2008-07-23
  • 打赏
  • 举报
回复
问题解决了,先给分。
老龙友 2008-07-23
  • 打赏
  • 举报
回复
[Quote=引用 17 楼 atlasroben 的回复:]
我说下我的思路你的问题可以简单化
线程池并发操作,当到了写入数据库的那一步的时候,将一个公共变量lock,当你数据写完以后就unlock,其他的线程在执行到写入数据库的代码的时候都会尝试lock那个公共变量,如果已经被lock了就会等待,直到unlock为止
[/Quote]

非常感谢!这个思路确实可以解决我的问题!
但我还有一个小问题,你也看到了,我的程序是每次运行这个子程序都会创建一个新的线程,我觉得这是一种不科学的办法。我很想改为使用线程池的方式来减少系统开销,但是我使用的时候,在tmpHO.HOitem下面会有波浪线,鼠标移动上去之后会提示:


system.Threading.ThreadPool.QueueUserWorkItem(New Threading.WaitCallBack(tmpHO.HOitem))

system.Threading.WaitCallBack 是委托类型,要求将单个"AddressOf"表达式作为构造函数的唯一参数。这是怎么回事?
ViewStates 2008-07-23
  • 打赏
  • 举报
回复
我做的那个和5楼类似。关于线程池只能有25个线程这个大可不必考虑,因为你再往线程池里丢任务的话如果线程池已经全部在用在它会把它放在一个队列里面的,然后就是FIFO方式。
我当时没有看过,是自己在外边套了个QUEUE,后来看了MSDN后才知道的。
你这个可以用SINGLETON来解决你并发导致的死锁的问题
atlasroben 2008-07-23
  • 打赏
  • 举报
回复
我说下我的思路你的问题可以简单化
线程池并发操作,当到了写入数据库的那一步的时候,将一个公共变量lock,当你数据写完以后就unlock,其他的线程在执行到写入数据库的代码的时候都会尝试lock那个公共变量,如果已经被lock了就会等待,直到unlock为止
老龙友 2008-07-23
  • 打赏
  • 举报
回复
难道真的要用 findcaiyzh 的办法了么

囧...
老龙友 2008-07-23
  • 打赏
  • 举报
回复
[Quote=引用 14 楼 atlasroben 的回复:]
你实际上的操作我能否这样理解
你多线程并发一系列复杂操作,当他们进行数据库操作的时候进行排队,让每个线程单独执行数据库的操作而不并行
[/Quote]

没错!就是这样,请问有什么好办法吗?
atlasroben 2008-07-23
  • 打赏
  • 举报
回复
你实际上的操作我能否这样理解
你多线程并发一系列复杂操作,当他们进行数据库操作的时候进行排队,让每个线程单独执行数据库的操作而不并行
atlasroben 2008-07-23
  • 打赏
  • 举报
回复
线程池中的线程是可以让你控制的
老龙友 2008-07-23
  • 打赏
  • 举报
回复
[Quote=引用 10 楼 atlasroben 的回复:]
多线程同步这块的复杂度比较高我不能给你详尽的代码来阐述这个问题,你可以参阅msdn的2个主要内容来解决你目前的问题
一个是线程池一个是同步控制 对应2个主要类 ThreadPool和Monitor msdn 上有比较详细的描述和实例
[/Quote]

你好。我现在的目标是希望:
如果使用线程池的话,能不能让我控制,一个线程执行完了才运行下一个线程,也就是串行而不是并行,因为根据我的理解只要是并行就可能产生死锁。。。
老龙友 2008-07-23
  • 打赏
  • 举报
回复
[Quote=引用 9 楼 atlasroben 的回复:]
C# code
using System;
using System.Threading;

// Note: The class whose internal public member is the synchronizing
// method is not public; none of the client code takes a lock on the
// Resource object.The member of the nonpublic class takes the lock on
// itself. Written this way, malicious code cannot take a lock on
// a public object.
class SyncResource {
public void Access…
[/Quote]

你好,C#的程序我看的很迷糊,能不能给我讲讲原理,知道了原理我就知道怎么用VB.NET去写代码了,谢谢!
atlasroben 2008-07-23
  • 打赏
  • 举报
回复
多线程同步这块的复杂度比较高我不能给你详尽的代码来阐述这个问题,你可以参阅msdn的2个主要内容来解决你目前的问题
一个是线程池一个是同步控制 对应2个主要类 ThreadPool和Monitor msdn 上有比较详细的描述和实例
atlasroben 2008-07-23
  • 打赏
  • 举报
回复

using System;
using System.Threading;

// Note: The class whose internal public member is the synchronizing
// method is not public; none of the client code takes a lock on the
// Resource object.The member of the nonpublic class takes the lock on
// itself. Written this way, malicious code cannot take a lock on
// a public object.
class SyncResource {
public void Access(Int32 threadNum) {
// Uses Monitor class to enforce synchronization.
lock (this) {
// Synchronized: Despite the next conditional, each thread
// waits on its predecessor.
if (threadNum % 2 == 0)
Thread.Sleep(2000);
Console.WriteLine("Start Synched Resource access (Thread={0})", threadNum);
Thread.Sleep(200);
Console.WriteLine("Stop Synched Resource access (Thread={0})", threadNum);
}
}
}

// Without the lock, the method is called in the order in which threads reach it.
class UnSyncResource {
public void Access(Int32 threadNum) {
// Does not use Monitor class to enforce synchronization.
// The next call throws the thread order.
if (threadNum % 2 == 0)
Thread.Sleep(2000);
Console.WriteLine("Start UnSynched Resource access (Thread={0})", threadNum);
Thread.Sleep(200);
Console.WriteLine("Stop UnSynched Resource access (Thread={0})", threadNum);
}
}

public class App {
static Int32 numAsyncOps = 5;
static AutoResetEvent asyncOpsAreDone = new AutoResetEvent(false);
static SyncResource SyncRes = new SyncResource();
static UnSyncResource UnSyncRes = new UnSyncResource();

public static void Main() {

for (Int32 threadNum = 0; threadNum < 5; threadNum++) {
ThreadPool.QueueUserWorkItem(new WaitCallback(SyncUpdateResource), threadNum);
}

// Wait until this WaitHandle is signaled.
asyncOpsAreDone.WaitOne();
Console.WriteLine("\t\nAll synchronized operations have completed.\t\n");

// Reset the thread count for unsynchronized calls.
numAsyncOps = 5;

for (Int32 threadNum = 0; threadNum < 5; threadNum++) {
ThreadPool.QueueUserWorkItem(new WaitCallback(UnSyncUpdateResource), threadNum);
}

// Wait until this WaitHandle is signaled.
asyncOpsAreDone.WaitOne();
Console.WriteLine("\t\nAll unsynchronized thread operations have completed.");
}


// The callback method's signature MUST match that of a
// System.Threading.TimerCallback delegate (it takes an Object
// parameter and returns void).
static void SyncUpdateResource(Object state) {
// This calls the internal synchronized method, passing
// a thread number.
SyncRes.Access((Int32) state);

// Count down the number of methods that the threads have called.
// This must be synchronized, however; you cannot know which thread
// will access the value **before** another thread's incremented
// value has been stored into the variable.
if (Interlocked.Decrement(ref numAsyncOps) == 0)
asyncOpsAreDone.Set();
// Announce to Main that in fact all thread calls are done.
}

// The callback method's signature MUST match that of a
// System.Threading.TimerCallback delegate (it takes an Object
// parameter and returns void).
static void UnSyncUpdateResource(Object state) {
// This calls the internal synchronized method, passing a thread number.
UnSyncRes.Access((Int32) state);

// Count down the number of methods that the threads have called.
// This must be synchronized, however; you cannot know which thread
// will access the value **before** another thread's incremented
// value has been stored into the variable.
if (Interlocked.Decrement(ref numAsyncOps) == 0)
asyncOpsAreDone.Set();
// Announce to Main that in fact all thread calls are done.
}
}

atlasroben 2008-07-23
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 andywongz 的回复:]
谢谢楼上的。

我听说线程池会最多启动25个线程
那如果这样的话,岂不还是会造成多个线程Update一个数据表,还是会造成死锁的?
线程池里的线程是一个一个排队执行的吗?如果是这样的话就好了。
[/Quote]
你可以控制他们在某处同步后再执行,执行完毕线程池回收
老龙友 2008-07-23
  • 打赏
  • 举报
回复
[Quote=引用 5 楼 findcaiyzh 的回复:]
我们项目的架构不知道你能不能借鉴。

输入来了之后,放到一个叫orders的表中,并标志为submitted。
一个windows service定时扫描这个表,取得所有submitted的order,并处理。
处理完之后标志为completed.

错误处理是个问题。我们的系统是给用户发邮件。
[/Quote]

你好,感谢回复。
你的这种办法我也是考虑过的,但是我们的实时性要求很强,如果当时扫描到有问题就必须马上提示用户。
你的办法也是一个解决方案,但是能不能用更简单的办法完成呢,我相信线程是可以实现这个功能的,只是我不知道该怎么去做。
老龙友 2008-07-23
  • 打赏
  • 举报
回复
谢谢楼上的。

我听说线程池会最多启动25个线程
那如果这样的话,岂不还是会造成多个线程Update一个数据表,还是会造成死锁的?
线程池里的线程是一个一个排队执行的吗?如果是这样的话就好了。
宝_爸 2008-07-23
  • 打赏
  • 举报
回复
我们项目的架构不知道你能不能借鉴。

输入来了之后,放到一个叫orders的表中,并标志为submitted。
一个windows service定时扫描这个表,取得所有submitted的order,并处理。
处理完之后标志为completed.

错误处理是个问题。我们的系统是给用户发邮件。
atlasroben 2008-07-23
  • 打赏
  • 举报
回复
线程池可以方便的解决你的排队等待入队回收线程资源等等问题,在你对多线程操作方面有良好的可用性,使你不用关心队列与资源的回收
至于你问及的控制问题需要如下类
System.Threading.Monitor
Monitor 具有以下功能:

它根据需要与某个对象相关联。

它是未绑定的,也就是说可以直接从任何上下文调用它。

不能创建 Monitor 类的实例。

将为每个同步对象来维护以下信息:

对当前持有锁的线程的引用。

对就绪队列的引用,它包含准备获取锁的线程。

对等待队列的引用,它包含正在等待锁定对象状态变化通知的线程。
atlasroben 2008-07-23
  • 打赏
  • 举报
回复
用线程池解决
简单示例

using System;
using System.Threading;
public class Example {
public static void Main() {
// Queue the task.
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));

Console.WriteLine("Main thread does some work, then sleeps.");
// If you comment out the Sleep, the main thread exits before
// the thread pool task runs. The thread pool uses background
// threads, which do not keep the application running. (This
// is a simple example of a race condition.)
Thread.Sleep(1000);

Console.WriteLine("Main thread exits.");
}

// This thread procedure performs the task.
static void ThreadProc(Object stateInfo) {
// No state object was passed to QueueUserWorkItem, so
// stateInfo is null.
Console.WriteLine("Hello from the thread pool.");
}
}


老龙友 2008-07-23
  • 打赏
  • 举报
回复
谢谢楼上的
可是怎么使用队列呢?我对线程操作不熟,麻烦指点下
加载更多回复(1)

16,722

社区成员

发帖
与我相关
我的任务
社区描述
VB技术相关讨论,主要为经典vb,即VB6.0
社区管理员
  • VB.NET
  • 水哥阿乐
  • 无·法
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧