彙總PyTorch踩過的10個坑
目錄
不一致的Flag
首先來看下邊這段程式碼。在程式碼中,我們開啟了兩個執行緒:主執行緒 mian 和ThreadDemo物件start的執行緒,我們叫它執行緒A。兩個執行緒擁有一個共享變數flag。
執行緒A在sleep 2000ms後將flag從false變為true,main執行緒死迴圈不斷去判斷flag是否為true,為true的時候print一串等號並退出迴圈。
public class TestVolatile { public static void main(String[] args) { ThreadDemo td = new ThreadDemo(); new Thread(td).start(); while (true){ if (td.isFlag()) { System.out.println("==============="); break; } } } } class ThreadDemo implements Runnable{ private boolean flag = false; @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } setFlag(true); System.out.println("flage = " + isFlag()); } public void setFlag(boolean flag) { this.flag = flag; } public boolean isFlag() { return flag; } }
此時我們預期的執行結果應該是兩個執行緒都啟動後,執行緒A sleep 2000ms後,將flag變為true,main執行緒列印一串等號。實際的執行結果如下:
可以看到兩個資訊
1- Flag的值確實被執行緒A改變了,改變之後已經輸出了flag = true
2- main執行緒並不知道flag已經改變了,因為它的死迴圈還在執行,也沒有打印出字串。
Flag不一致的原因
出現這種情況的原因在於每條執行緒都擁有自己單獨的緩衝區來儲存變數,所以線上程執行的時候,變數會在主存中儲存一份,每條執行緒會從主存複製一份變數到自己的緩衝區以提高效率。在我們上邊這段程式碼中,flag = false首先被分發到main執行緒和執行緒A,兩個執行緒都正常執行,當執行緒A將flag置為true的時候,會將該操作同步到主存的flag中,可是main執行緒在執行死迴圈,它比較忙,沒有時間到主存中同步資料,導致main執行緒的flag值不會被更新,死迴圈不會跳出。
使用Synchronized關鍵字解決
flag是td物件的屬性值,使用synchronized關鍵字給td加同步鎖,讓每次使用到td物件時都強制去主存中同步資料,可以解決這個問題,程式碼如下
public class TestVolatile { public static void main(String[] args) { ThreadDemo td = new ThreadDemo(); new Thread(td).start(); while (true){ //同步鎖,保證每次都去主存中同步資料 synchronized (td) { if (td.isFlag()) { System.out.println("==============="); break; } } } } } class ThreadDemo implements Runnable{ private boolean flag = false; @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } setFlag(true); System.out.println("flage = " + isFlag()); } public void setFlag(boolean flag) { this.flag = flag; } public boolean isFlag() { return flag; } }
程式碼執行結果
可以看到程式碼執行結束了,main執行緒也退出了死迴圈。
但是,因為synchronized擁有互斥性,加鎖會讓程式碼執行效率變低很多。想要同時保證執行效率和同步主存資料,就需要使用volatile關鍵字。
volatile關鍵字
當多個執行緒操作共享資料,可以保證記憶體中的資料是可見的.使用了記憶體柵欄,可以理解為每個執行緒使用和操作的都是主存中的資料.
public class TestVolatile {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while (true){
if (td.isFlag()) {
System.out.println("===============");
break;
}
}
}
}
class ThreadDemo implements Runnable{
//此處增加了volatile關鍵字
private volatile boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
setFlag(true);
System.out.println("flage = " + isFlag());
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public boolean isFlag() {
return flag;
}
}
程式碼執行結果
可以看到,程式碼還是可以正常執行的。
Synchronized與Volatile的區別
相較與synchronized,volatile是一種較為輕量級的同步策略,但兩者之間存在不通.
1- volatile不具備互斥性
2- volatile不能保證變數的原子性
至於Volatile關鍵字對指令重排的影響等,後續增加。