有個簽名的函式。因為時常要同時簽名多條資料。 假設每次簽名需要10ms時間。我一次需要簽名10條資料。順序簽名需要100ms。按理說我開10個TASK 同時簽名 按理說如果cpu資源“足夠”的情況下 TASK完成時間應該是10ms加上一點損耗。。 但是實際上 TASK完成時間有時候甚至能超過100ms(順序運行的總和時間)。。后來去查了一下資料。發現很多語言都有執行緒鎖。實際只是“并發”,而不是“并行”在計算。
為了驗證是否硬體資源問題。我嘗試用多個行程跑同樣的連續單執行緒簽名的程式。得到的結果也也似乎驗證了 執行緒只是并發運行。(多個行程下。每個行程使用的簽名時間都是10ms,當然有誤差和cpu占用的損耗)。。
所以。我想請問2個問題
1,C# 能否自身做到真正的“并行”多執行緒執行?
2,如果無法并行處理。那么我打算用多行程方式解決。那么請問為了降低兩個行程間的延遲。請問用那種多行程間通訊方式?大概查了一下。有ipc、共享記憶體等。。 。我是在.net core下用。。程式在windows下開發除錯。在linux下運行。 也就是說最好能兼容兩種系統。并且通訊延遲要越低越好。至少在毫秒內的。請問我應該著重去找哪方面的資料?
uj5u.com熱心網友回復:
這個問題承接你上一個,你說的很對執行緒只是一種利用短暫時間片切換并發執行,造成表象上的并行手段(因為cpu運算快,而一個時間片只有幾納秒,所以感官上他是連續執行)
當然涉及到執行緒切換,所以你加入的執行緒越多,分配到每個執行緒的總執行時間就會相應延長
所以就產生的多核運算,而在netcore里,多核運算為Parallel 并行計算,他是對多核cpu支持優化,也就是盡量把你的執行緒任務分給不同的cpu,這樣他才是真正的同時執行
另外在補充一下你上個帖子的Gc影響
1.大多數討論gc對程式影響的討論,發生在程式占用大量物理記憶體,也就是兩類
一:你程式快速產生大量1代物件,而且都在用gc不能給你釋放
二:你的程式有嚴重的記憶體泄漏,不在gc的管控范圍
這樣如果你程式占用記憶體超過800M,而且在上面兩種情況,gc一次清理,也就只能騰出1-2m,此時在申請記憶體,就會強迫gc去清理,這樣造成頻繁gc動作,幾乎每次都要去清理,那么就會慢
這點東西,我們看我們的C盤就好,如果說剛裝機C盤剩下60G,我3個月清理一回都行,因為他足夠分,3月清理一會一次釋放個20G都成
但是如果隨著時間推進,我們裝的東西一多,就剩6-12G的時候,我就幾天清理一次都不行。基本上到那個時候,C盤都是我們必須要的,在清理都請清不出來啥,即使我天天清理,也就勉強維持在8G上下。
所以嚴格上說gc對程式有影響,但他發生在總物理記憶體占用超過90%,Gc必須每個動作都要清理一遍的情況。所以還是前面的結論,我們首先要討論的事情是為啥會存在那么多Gc清不動的東西,,對,我們對待C盤的態度一樣,如果占用超過90%,我們只能去看到底C盤裝了啥,為啥C盤有那么多,不能清,也不敢清,或者我都不知道他那里用的東西--------卸(內)載(存)遺(泄)留(漏)
uj5u.com熱心網友回復:
實際上你的問題,更多情況下我們是拆分任務我們既然知道多執行緒切換總會消耗時間,那么我們就需要拆成很多小任務,這些小任務他可以很快走掉,走掉了。就不參與
另外一個是合理分派IO任務和計算任務。IO任務總體上不怎么消耗cpu,計算任務才消耗cpu
所以我們對于你這樣的計算簽名這樣的東西,我們現在通常寫的是
using(steam sm=awaite 異步io給他一個stream)
{
response xx=await 計算前面(sm)
string xxx= await response.getStringAsync()
}
這樣他實際上是3個更小的執行緒,其中前后兩個是IO任務,中間那個是計算任務,當然這些東西因為不涉及的UI部分,所以你可以用ConfigureAwait(false),讓他不去在后續任務程序去捕獲背景關系,已加快切換速度
uj5u.com熱心網友回復:
并行計算是否可縮短時間,也要看任務本身。單個任務如果已經將你的cpu吃滿了,那么并行計算就沒有什么意義了。
但是如果是以下情況,并行就有明顯優勢了:
任務本身不耗計算資源,但是耗時較長,如需要等待的任務,常見的瀏覽廣告頁面(比如90秒),送積分任務。假如你有1000個賬號要做這個任務,順序執行的話時間大概就是90*1000秒;并行計算必定會快非常多,優化后甚至可以無限接近90s。
uj5u.com熱心網友回復:
parallel. invoke()我記得可以這樣并行執行
uj5u.com熱心網友回復:
話說,我實際測驗一下,其實并沒有發現你說情況測驗代碼
static async Task Main(string[] args)
{
string x = string.Join("", Enumerable.Repeat(1, 100));
Stopwatch watch = new Stopwatch();
watch.Start();
await Task.WhenAll(getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x),
getMD5(x), getMD5(x)
);
watch.Stop();
Console.WriteLine(watch.Elapsed.TotalMilliseconds);
Console.ReadKey();
}
static MD5 md5 = MD5.Create();
public static async Task<string> getMD5(string str)
{
using (Stream ms = await CreateStrem(str).ConfigureAwait(false))
{
var x = await Task<byte[]>.Run(() =>
{
return md5.ComputeHash(ms);
}).ConfigureAwait(false);
return UTF8Encoding.UTF8.GetString(x);
}
}
public static Task<Stream> CreateStrem(string x)
{
return Task<Stream>.Run(async () =>
{
MemoryStream ms = new MemoryStream();
await ms.WriteAsync(UTF8Encoding.UTF8.GetBytes(x));
ms.Seek(0, SeekOrigin.Begin);
return (Stream) ms;
});
}
除了統計輸出,我沒有加入其他任何輸出,因為控制臺輸出會干擾統計。
實際結果:單獨運算一個和并發10個沒有任何區別,我測驗機跑一個22ms,跑10個還是22ms。其實22ms對于計算機來說是無壓力的,進的快,走的也快(小學題:一個進水,一個出水。22ms對計算機來說近乎是進水等于出水的節奏,)
ps:22ms是在控制臺直接運行,不是在vs里跑的,vs跑的快要*10,因為vs啟動診斷等等附加開銷
uj5u.com熱心網友回復:
await Task.WhenAll(getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x),
getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x),
getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x),
getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x),
getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x),
getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x),
getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x),
getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x),
getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x),
getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x),
getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x), getMD5(x),
getMD5(x), getMD5(x)
我加這么多跑,結果75ms,可見他當然是并行的
uj5u.com熱心網友回復:
感謝解答。。經過測驗和找了一些粗淺的資料 嘗試用 如下類似的方法來測驗同時簽名 正常情況下函式執行時間在5ms左右。我測驗同時簽名6條資料(cpu 6核,盡量減低cpu本身能力不足的原因,所以用6條測驗)。實際 測驗下來雖然比逐條運行有所改善,但是遠沒有達到期望的5ms+損耗就能出結果的需求。我粗淺的認為 這里的Parallel 并不是我想要的“并行”。所以想嘗試多行程方案。所以想再次請教2個問題1,我的測驗是否正確?還是還有其他方案?
2,假設我想要嘗試多行程方案。請推薦一種方案可以么?沒接觸過多行程。因為是想提高并發的簽名速度,所以希望行程間的通訊延遲足夠小,至少ms內其他行程能做出回應并且執行簽名任務后主行程也能最快速度得到回饋。。需兼容Linux/windows平臺。
Parallel.ForEach(list, new ParallelOptions() { MaxDegreeOfParallelism = 6 }, (i) =>
{
string tmp = 純cpu運算操作簽名異步函式,是一個橢圓ECDSA演算法(1).Result;
});
uj5u.com熱心網友回復:
很是奇怪。我之前就用task。whenAll做的測驗。測驗結果不但沒有減少簽名時間。反而偶爾有可能比順序總時間還增加了。我呼叫的簽名如下:不知道您能否幫助看下問題所在。。
using Nethereum.Signer
簽名代碼
static EthereumMessageSigner signer = new EthereumMessageSigner();
byte[] array = Encoding.ASCII.GetBytes(message);
string tmp = signer.Sign(array, _ethPrivateKey);
uj5u.com熱心網友回復:
string tmp = 純cpu運算操作簽名異步函式,是一個橢圓ECDSA演算法(1).Result;result 是一個同步阻斷操作,他和真異步的區別是,此時你的執行緒還在運行(執行緒有執行緒,這個result就是說該該步操作依然在執行緒調度中輪詢)
真異步是 await 另一個task 或者await 驅動級別的IO信號,此時本執行緒處于掛起狀態或者完成狀態,不參與執行緒調度輪詢,直到他收到信號量或者回呼才會從新執行
其實真await task 是一個狀態機遷移動作
他真實描述是
(i) =>
{
//這個新執行緒1
string tmp = 純cpu運算操作簽名異步函式,是一個橢圓ECDSA演算法(1).Result; //另啟動執行緒,但本執行緒不退出,阻塞拿結果
還是這個新執行緒1
});
async(i) =>
{
//這個新執行緒1
string tmp =await 純cpu運算操作簽名異步函式,是一個橢圓ECDSA演算法(1). // 另啟動執行緒2,狀態遷移,當前執行緒退出或掛起 ,此刻實際運行 執行緒2,執行緒1已退出 或掛起
//另外執行緒執行完畢,狀態遷移,執行緒2回呼到此或者信號量釋放執行緒1恢復
});
對比真異步的化你就知道,同步拿結果。他其實是一個阻止更多并行的操作
uj5u.com熱心網友回復:
Sign 以上呼叫的是第三方庫。當時以為sign中會有什么其他操作。我單獨吧Sign抽離出來也是同樣結果。抽離出來的代碼如下。也就是sign的大概流程
public static string Sign(byte[] message, ECDsaSigner _ECDsaSigner, byte[] uncompressedPublicKey/*, ECKey _ecKey*/)
{
DateTime d = DateTime.Now;
DateTime d2 = DateTime.Now;
var byteList = new List<byte>();
var bytePrefix = "0x19".HexToByteArray();
var textBytePrefix = Encoding.UTF8.GetBytes("Ethereum Signed Message:\n" + message.Length);
byteList.AddRange(bytePrefix);
byteList.AddRange(textBytePrefix);
byteList.AddRange(message);
var plainMessage = byteList.ToArray();
Console.WriteLine($"步驟:1 {(DateTime.Now - d)}");
d = DateTime.Now;
KeccakDigest digest = new KeccakDigest(256);
byte[] hash = new byte[digest.GetDigestSize()];
digest.BlockUpdate(plainMessage, 0, plainMessage.Length);
digest.DoFinal(hash, 0);
Console.WriteLine($"步驟:2 {(DateTime.Now - d)}");
d = DateTime.Now;
var tmp = _ECDsaSigner.GenerateSignature(hash);
Console.WriteLine($"步驟:3 {(DateTime.Now - d)}");
d = DateTime.Now;
var tmp2 = new ECDSASignature(tmp);
Console.WriteLine($"步驟:4 {(DateTime.Now - d)}");
d = DateTime.Now;
var byte_tmp = tmp2.ToDER();
Console.WriteLine($"步驟:5 {(DateTime.Now - d)}");
d = DateTime.Now;
var sig = ECDSASignature.FromDER(byte_tmp);
Console.WriteLine($"步驟:6 {(DateTime.Now - d)}");
d = DateTime.Now;
var ECDSAsignature = sig.MakeCanonical();
Console.WriteLine($"步驟:7 {(DateTime.Now - d)}");
d = DateTime.Now;
var recId = -1;
for (var i = 0; i < 4; i++)
{
ECKey rec = ECKey.RecoverFromSignature(i, ECDSAsignature, hash, false);
if (rec != null)
{
var k = rec.GetPubKey(false);
Console.WriteLine($"步驟:8-{i} {(DateTime.Now - d)}");
if (k != null && k.SequenceEqual(uncompressedPublicKey))
{
recId = i;
break;
}
}
}
if (recId == -1)
throw new Exception("Could not construct a recoverable key. This should never happen.");
Console.WriteLine($"步驟:8 {(DateTime.Now - d)}");
d = DateTime.Now;
ECDSAsignature.V = new[] { (byte)(recId + 27) };
var t2 = "0x" + ECDSAsignature.R.ToByteArrayUnsigned().ToHex().PadLeft(64, '0') +
ECDSAsignature.S.ToByteArrayUnsigned().ToHex().PadLeft(64, '0') +
ECDSAsignature.V.ToHex();
Console.WriteLine($"步驟:9 {(DateTime.Now - d)}");
Debug.WriteLine($"{DateTime.Now.ToString("mm:ss:ffff")} 簽名總耗時 {(DateTime.Now - d2)}");
return t2;
}
代碼中有一些檢查簽名耗時的一些嬰兒代碼。別見怪。。~~為了速度還吧一些物件提前實體化,靜態化。隨意引數有所改動。但是實際這些耗時都不高。
最耗時的是var tmp = _ECDsaSigner.GenerateSignature(hash); 和
ECKey rec = ECKey.RecoverFromSignature(i, ECDSAsignature, hash, false);
這兩條陳述句。是個橢圓DSA演算法。。也已經獨立抽離出來。但是沒能力簡化和提高運算速度。。
uj5u.com熱心網友回復:
Parallel在多核(多執行緒)cpu上就是真正的并行,只不過每啟動其中一個任務也是需要時間的, 你別用5ms的任務試, 換用5s的試試.uj5u.com熱心網友回復:
我覺著你最需要知道的執行緒本身,而不是語法本身。你好像總在繞圈https://www.zhihu.com/question/42962803
你的知道你的執行緒到底在什么狀態,result同步的情況他一直處于運行態,而async await 其實是本執行緒已死或者掛起(邏輯上我們認為他是掛起狀態,但其實是執行緒已死,如果你在 await task 進入前,執行中,重新運行回來這3個地方輸出執行緒id,你會發現進入前是一個id,重新回來后是另一個id,說明邏輯上好像在同一個大括號里,但實際上他被編輯器編譯成了 傳統的 這種回呼方式)
執行緒1
{
call執行緒2(引數背景關系,回呼方法)//call完了就死了
}
執行緒2
{
有結果了,修改引數背景關系,Call回呼方法(修改后的引數背景關系)
}
回呼方法(引數背景關系)
{
對,我們這里拿到新結果了,但此時其實在執行緒2里,而不是執行緒1,執行緒1早就死了
}
這樣你會發現就是我們說的,他拆成更小的任務,同時他在執行狀態中的就1個執行緒。而同步reslut,這個確有兩個執行中的執行緒并且這兩個執行緒還有同步關系
uj5u.com熱心網友回復:
而同步reslut,這個確有兩個執行中的執行緒并且這兩個執行緒還有同步關系這個就相當于很多人最喜歡用的Thread.Join ,但是這東西的結果就是多出一個執行緒,而且這兩執行緒還不是并行關系,這兩執行緒是串行關系
uj5u.com熱心網友回復:
非常抱歉。。 .Result是在發帖的時候我給特意加上了。理解不夠深。因為我發現用如下代碼 會造成 Debug.WriteLine($"并行總時間{DateTime.Now - d}"); 執行的過早。。也就是沒能等 Parallel.ForEach 全部執行完成就執行了這個debug輸出。。 因為是測驗。所以沒去學習這門等待Parallel.ForEach完整完成的方法。就想當然的改成.Result 發帖詢問了。。
Debug.WriteLine("并行 Test start");
Parallel.ForEach(list, new ParallelOptions() { MaxDegreeOfParallelism = 6 }, async (i) =>
{
Debug.WriteLine($"并行 {i} 開始 {DateTime.Now.ToString("mm:ss:ffff")}");
var tmp = await TestAsync1(1);
});
Debug.WriteLine($"并行總時間{DateTime.Now - d}");uj5u.com熱心網友回復:
額。那么按照我的要求。我只能去尋求多行程的解決方法了么?總覺得多行程可能會有點麻煩。。。Parallel的啟動耗時挺多的么?。另外。請教。假設我task的數量是確定的。能否指定.Parallel的起始數量? 默認有個引數是指定最大并行數。。
uj5u.com熱心網友回復:
你太過糾結語法,先去看執行緒本身。你現在這樣問其實就是執行緒狀態問題。執行緒的第一,第2步狀態是,建立---就緒
然后是等待作業系統調度,等待cpu調度。不是什么啟動耗時挺多,而是由系統根據優先級調度,你用的task.run其實也是多核調度,他是執行緒池,你先進池,然后執行緒池調度器給你調度,執行緒池調度器也是支持多核的。(只是執行緒池比多核出的早,所以執行緒池對多核利用不太充分,所以后面才有Paralle,你不需要要糾結什么Paralle核執行緒,因為Paralle其實依舊是執行緒池,只是對新的多核指令集進行了優化,對,硬體廠家出多核方案后調整了指令集的,對多核特性上出來專用指令集,當然這個都不關你的事情,你只需要做好自己的事情)
做好你自己的事情
1.劃分小任務
2.區分IO任務,cpu任務
3.對于IO任務,請及時釋放物件和記憶體
4.cpu任務,在滿足要求的情況,適當控制并發數量。(池 或者 帶數量控制的phore信號量-------也就是12306的賣票機制,我設計目標達產多少,滿產多少就發多少票,不要超賣,把東西一股腦全扔到執行緒)
System.Threading.SemaphoreSlim SemaphoreSlim=new SemaphoreSlim(100,200); //達產100,滿產200
還是小學題,一進一出,問多長時間能放完。我們說這取決與你進多少,出多少。當然我得限制進入口徑,不做限制,你進水量是1000,我出水量最多190,結果當然是悲劇
uj5u.com熱心網友回復:
感謝解答。。經過測驗和找了一些粗淺的資料 嘗試用 如下類似的方法來測驗同時簽名 正常情況下函式執行時間在5ms左右。我測驗同時簽名6條資料(cpu 6核,盡量減低cpu本身能力不足的原因,所以用6條測驗)。實際 測驗下來雖然比逐條運行有所改善,但是遠沒有達到期望的5ms+損耗就能出結果的需求。我粗淺的認為 這里的Parallel 并不是我想要的“并行”。所以想嘗試多行程方案。所以想再次請教2個問題
1,我的測驗是否正確?還是還有其他方案?
2,假設我想要嘗試多行程方案。請推薦一種方案可以么?沒接觸過多行程。因為是想提高并發的簽名速度,所以希望行程間的通訊延遲足夠小,至少ms內其他行程能做出回應并且執行簽名任務后主行程也能最快速度得到回饋。。需兼容Linux/windows平臺。
Parallel.ForEach(list, new ParallelOptions() { MaxDegreeOfParallelism = 6 }, (i) =>
{
string tmp = 純cpu運算操作簽名異步函式,是一個橢圓ECDSA演算法(1).Result;
});
string tmp = 純cpu運算操作簽名異步函式,是一個橢圓ECDSA演算法(1).Result;
這個寫法,就是同步。
改成下面的試試看
Parallel.ForEach(list, new ParallelOptions() { MaxDegreeOfParallelism = 6 }, async (i) =>
{
string tmp = await 純cpu運算操作簽名異步函式,是一個橢圓ECDSA演算法(1);
});
uj5u.com熱心網友回復:
我記得吧。對于單CPU來說,不存在并行,這與語言是無關的。執行緒的增加反而占用了計算資源。燒水的時候同時掃地正是習以為常的并發,人是cpu,燒水是執行緒、掃地是執行緒。
*傻等水開了再去掃地、和燒水時掃地實際上人這個CPU的作業量是沒有變化的。
你這個作業如果各個任務不存在阻塞/等待/...情況的話。同樣演算法、代碼下,單CPU下不存在提高效率,反而只會增加損耗【執行緒切換...】
思路有三個:
增加CPU/強化CPU
優化演算法
優化代碼
具體我沒經驗,不懂,但方向是這個不會錯
uj5u.com熱心網友回復:
如果是個CPU的計算機,我記得好像也需要一些特定的代碼才能讓執行緒分別并行作業起來,也就是也需要對應代碼形式,否則不一定完全在并發。以前學習時了解的,不知道有沒有記錯
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/276497.html
標籤:C#
上一篇:多執行緒的問題
