其實說多執行緒修改資料也不合適,畢竟redis服務端是單線程的,所有命令串行執行,只是在客戶端並發發送命令的時候,導致串行的命令一些排列問題和網路時間差等造成資料不一致。本文雖然是數字的加減,但為了說明鎖的情況,故意不是用原子指令incr。 (推薦:redis影片教學)
先配上一個簡易的RedisHelper,一個set值,一個get值,一個設定並發鎖,以便在我後面的操作中,你能清楚我究竟做了什麼。
public class RedisHelper { public RedisClient client = new RedisClient("127.0.0.1", 6379); public void Set<T>(string key, T val) { client.Set(key, val); } public T Get<T>(string key) { var result = client.Get<T>(key); return result; } public IDisposable Acquire(string key) { return client.AcquireLock(key); } }
下面看一下並發程式碼,我只new了兩個Thread。兩個執行緒同時想存取同一個key,分別存取五萬次,在並發條件下,我們很難保證資料的準確性,請比較輸出結果。
static void Main(string[] args) { RedisHelper rds = new RedisHelper(); rds.Set<int>("mykey1", 0); Thread myThread1 = new Thread(AddVal); Thread myThread2 = new Thread(AddVal); myThread1.Start(); myThread2.Start(); Console.WriteLine("等待两个线程结束"); Console.ReadKey(); } public static void AddVal() { RedisHelper rds = new RedisHelper(); for (int i = 0; i < 50000; i++) { int result = rds.Get<int>("mykey1"); rds.Set<int>("mykey1", result + 1); } Console.WriteLine("线程结束,输出" + rds.Get<int>("mykey1")); }
是的,跟我們單線程,跑兩個50000,會輸出100000。現在是兩個並發線程同時跑在由於並發造成的數據結果往往不是我們想要的。那麼要如何解決這個問題呢,Redis已經為我們準備好了!
你可以看到我RedisHelper中有個方法是 public IDisposable Acquire(string key)。 也可以看到他回傳的是IDisposable,證明我們需要手動釋放資源。
方法內部的 AcquireLock正是關鍵之處,它像redis中索取一把鎖頭,被鎖住的資源,只能被單個線程訪問,不會被兩個線程同時get或者set,這兩個線程一定是交替著進行的,當然這裡的交替並不是指你一次我一次,也可能是你多次,我一次,下面看代碼。
static void Main(string[] args) { RedisHelper rds = new RedisHelper(); rds.Set<int>("mykey1", 0); Thread myThread1 = new Thread(AddVal); Thread myThread2 = new Thread(AddVal); myThread1.Start(); myThread2.Start(); Console.WriteLine("等待两个线程结束"); Console.ReadKey(); } public static void AddVal() { RedisHelper rds = new RedisHelper(); for (int i = 0; i < 50000; i++) { using (rds.Acquire("lock")) { int result = rds.Get<int>("mykey1"); rds.Set<int>("mykey1", result + 1); } } Console.WriteLine("线程结束,输出" + rds.Get<int>("mykey1")); }
可以看到我使用了using,呼叫我的Acquire方法取得鎖定。
輸出結果最後是100000,正是我們要的正確結果。前面的8W 是因為兩個執行緒之一先執行結束了。
還有,在正式使用的過程中,建議給我們的鎖,使用後刪除掉,並加上一個過期時間,使用expire。
以免程式執行期間意外退出,導致鎖定一直存在,今後可能無法更新或取得此被鎖定的資料。
你也可以嘗試不設定expire,在程式剛開始執行時,關閉console,重新執行程序,並且在redis-cli的操作控制台,get你鎖住的值,將會永遠取得不到。
所有連接此redis實例的機器,同一時刻,只能有一個取得指定name的鎖定。
#下面是StackExchange.Redis的寫法
var info = "name-"+Environment.MachineName; //如果5秒不释放锁 自动释放。避免死锁 if (db.LockTake("name", info, TimeSpan.FromSeconds(5))) { try { } catch (Exception ex) { } finally { db.LockRelease("name", token); } }
更多redis知識請關注redis資料庫教學欄位。
以上是Redis鎖的簡單應用介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!