比如我现在有10000条任务需要执行,通过信号量SemaphoreSlim来实现控制20条线程并发。
在线程内部是发送GET请求,由于目标服务器上面有限制,可能会返回403,其中任意一条线程发现返回的状态是403的时候进行拨号换IP,现在问题就是怎么通知其他线程,我已经换IP了,你们不用再换了,注意,通知的线程只是在这20次中的线程,后来的线程不做通知,因为服务器可能又限制了。
这个是我现在的做法,定义一个程序集变量,在线程内部获取这个变量_flag
然后在异常块判断,这两个值是否相等,不相等说明已经更换IP了,那么重新执行这个方法,如果相等则进入lock块再判断是否更换IP,有点像单例。
想请问一下是否还有更好的做法?
你中间用Monitor#Wait和PulseAll不就解决线程中通信的问题了么?
您没理解到问题点,问题是我只控制这一批的线程,后来的线程归后来的管,这里面会有一个数据丢失的问题,我现在的做法把数据丢失的可能性减少了,我想问的是有没有更好的办法
@梦里的畅泳: 我理解你的原始问题是在这些线程执行时可能需要做下某些信息的变更,但这种变更只需要在出现这种情况下变化一次而不是所有线程都做这种变化,这个不就是通过monitor来解决么?没明白你所谓的丢失数据的情况在哪
@Daniel Cai: 我表达能力不是很好,你说的这种方案我考虑过,解决不了问题,因为在同一时间会有多个线程进入异常块来跟换IP,同时后面还会进入新线程,新进来的线程是要另外处理的,锁达不到要求
@梦里的畅泳:
class OneThreadChanger
{
static object syncRoot=new object();
volatile static Thread ownerThread;
static string targetIp;
public static string ChangeIp()
{
lock(syncRoot)
{
if(ownerThread==null)
ownerThread=Thread.CurrentThread;
}
if(ownerThread==Thread.CurrentThread){
//...change ip process
targetIp=....;
Monitor.PulseAll(ownerThread);
ownerThread=null;
}else
Monitor.Wait(ownerThread);
return targetIp;
}
}
@Daniel Cai: 您这个只能控制我这一次的并发出来的线程。
SemaphoreSlim _slim = new SemaphoreSlim(20); private async void Form1_Load(object sender, EventArgs e) { for (int i = 0; i < 10000; i++) { await _slim.WaitAsync(); Task.Factory.StartNew(() => { StartTask("",""); }); } } private void StartTask(string id,string token) { try { //这里执行网页访问代码 } catch (Exception exception) { //进入catch块说明需要更换IP //在同一时间并发出来的线程,我只需要其中的一条线程来更换IP,其他线程等待这个线程更换IP然后重新调用 StartTask //但是不影响后来并发出来的线程,其实前面线程在等待,没有释放信号后面线程也是无法继续的 } finally { _slim.Release(); } }
@梦里的畅泳: 我明白你的意思了,这个我明天写给你吧
@Daniel Cai: 好的,感谢!
@梦里的畅泳:
class IpGetter { private int myTag=0; private string usedIp; private object lockObj=new object(); private object notifyObj=new object(); public IpGetter() { bool success=false; do{ try { usedIp=GetIpInternal(); success=true; } catch { success=false; Thread.Sleep(1); } }while(!success) } public string GetUseableIp(IIdentifiable identifiableObj) { if(identifiableObj.Tag!=Volatile.Read(ref myTag)) { if(Monitor.TryEnter(lockObj)) { try { usedIp=GetIpInternal(); identifiableObj.Tag=myTag; Monitor.PulseAll(notifyObj); } finally { Monitor.Exit(lockObj); } } else { Monitor.Wait(notifyObj); identifiableObj.Tag=myTag; } } return usedIp; } private string GetIpInternal(){} public void ReportNotUseable(IIdentifiable identifiableObj) { int tmp=myTag; Thread.MemoryBarrier(); tmp=tmp+1; Interlocked.CompairExchange(ref myTag,tmp,identifiableObj.Tag); } } interface IIdentifiable { int Tag{get;set;} }
大体思想是要尽量减少出错(因此每次使用时需要问IpGetter#GetUseableIp要最新的ip),同时又要减少无谓的更换次数(所以提出了IIdentifiable接口,通过这个接口中的Tag表示任务正在使用什么版本的ip信息,如果一旦在某次使用时候出现问题则立即通过IpGetter#ReportNotUseable进行通知)
看似搞得好高级似的 ——
不知道理解有没有问题,用一些拨号资源后需要换IP?
如果是这样不需要那么复杂,一个静态变量,set的时候lock起来就行了。
大致是这样,但是需要考虑到重试的问题,而且这次换IP只通知同一时间并发出来的20条请求
比如说,第一次20条,在10条出错了需要换IP,那么只通知其他10条,我这条现在在换IP你们不用换了等我换完再重新访问就可以了,但是后面进来的线程是不通知的,因为后面进来的可能还需要换IP了
@梦里的畅泳: 一个共享静态变量,set的时候lock起来,失败的重试即可。
@花飘水流兮: 不好意思,我不太明白您的意思,您能提供一下代码吗?我理解您的这种方式和我现在的这个感觉上是一样的,还是我理解错误了呢?
@梦里的畅泳:
static string Ip{get;set;}
Task.Run(()=>{
var responseIp = Ip; //初始化
这里你会处理到responseIp;isFailed(代表是否403)
if(isFailed)
{
if(responseIp != Ip)lock(Ip){Ip = responseIp ;//这里看你实际情况还要不要加策略,比如测试成功才设置}
else 这里就是重试等机制了,自己屡一下逻辑和 以及 策略。
}
})
@花飘水流兮: 您这个代码和我代码没区别啊
int _flag = DateTime.Now.Ticks;这样能算出来时间差。
每个执行时间不一样的