多執行緒、程序鎖機制的作用
阿新 • • 發佈:2021-02-11
為了最大程度上了解鎖的機制,此處使用C++來進行編碼
(python其實也比較好哈哈哈哈)
以下程式碼為不加鎖的時候的程式碼,毫無疑問是亂序的,因為兩個執行緒同時操作了全域性變數
#include <thread>
#include <iostream>
#include <mutex>
#include <list>
// 全域性變數
std::mutex my_mutex;
list<int> L;
// 寫入函式
void func1() {
for (int i = 0; i < 1000; i++) {
std::cout << "插入的資料為:" << i << std::endl;
L.push(i);
}
}
// 讀取函式
void func2() {
for(int i = 0; i < 1000; i++) {
std::cout << "讀取的資料為:" << L.front() << endl;
L.pop_front();
}
}
int main(int argc, char const* argv[]) {
std::thread t1 (func1);
std::thread t2(func2);
t1.join();
t2.join();
return 0;
}
下面有兩種加鎖方式,一種是全部寫入再全部讀出
#include <thread>
#include <iostream>
#include <mutex>
#include <list>
// 全域性變數
std::mutex my_mutex;
std::list<int> L;
// 寫入函式
void func1() {
std::lock_guard<std::mutex> g1(my_mutex) ;
for (int i = 0; i < 1000; i++) {
std::cout << "插入的資料為:" << i << std::endl;
L.push_back(i);
}
}
// 讀取函式
void func2() {
std::lock_guard<std::mutex> g1(my_mutex);
for (int i = 0; i < 1000; i++) {
if (!L.empty()) {
std::cout << "讀取的資料為:" << L.front() << std::endl;
L.pop_front();
}
}
}
int main(int argc, char const* argv[]) {
std::thread t1(func1);
std::thread t2(func2);
t1.join();
t2.join();
return 0;
}
可以看到,完全沒有問題
第二種是一邊寫入一邊讀出!!
#include <thread>
#include <iostream>
#include <mutex>
#include <list>
// 全域性變數
std::mutex my_mutex;
std::list<int> L;
// 寫入函式
void func1() {
// std::lock_guard<std::mutex> g1(my_mutex);
for (int i = 0; i < 1000; i++) {
std::cout << "插入的資料為:" << i << std::endl;
std::lock_guard<std::mutex> g1(my_mutex);
L.push_back(i);
}
}
// 讀取函式
void func2() {
// std::lock_guard<std::mutex> g2(my_mutex);
for (int i = 0; i < 1000; i++) {
std::lock_guard<std::mutex> g2(my_mutex);
if (!L.empty()) {
std::cout << "讀取的資料為:" << L.front() << std::endl;
L.pop_front();
}
}
}
int main(int argc, char const* argv[]) {
std::thread t1(func1);
std::thread t2(func2);
t1.join();
t2.join();
return 0;
}
這個過程我來稍微解釋一下蛤:
- 對兩個執行緒進行阻塞,防止主執行緒執行完之前子執行緒沒有執行完
- 第一個子執行緒在迴圈內加鎖後對全域性變數L進行寫入一個數據,然後解鎖,此時L中有一個數據即1
- 全域性變數此時是被解鎖的被釋放的資源,而此時有兩個執行緒是阻塞的狀態,在等待事件,而此時L是被釋放,而在兩個子執行緒之中都可以對L進行操作,此時就要競爭資源,哪一個執行緒先執行就先進行加鎖和操作再解鎖
- 依次迴圈往復,直到競爭的次序已經耗盡,所以執行緒結束。
- 注意:執行緒在競爭資源的時候執行緒也是在執行的,只是阻塞的時候在等待事件,如果競爭成功了可以進入加鎖,沒有競爭成功只有失去了這一次機會,每次只有一個執行緒可以向前執行
之前看到一句話挺有道理:
如果釋放互斥鎖時有多個執行緒阻塞,所有在該互斥鎖上的阻塞執行緒都會變成可執行狀態,第一個變為執行狀態的執行緒可以對互斥量加鎖,其他執行緒將會看到互斥鎖依然被鎖住,只能回去再次等待它重新變為可用。在這種方式下,每次只有一個執行緒可以向前執行
python的話我也不介意寫一下
第一種情況
from threading import Thread
from threading import Lock
from queue import Queue
# 定義全域性變數
list_thread = []
lock = Lock()
def func1():
lock.acquire()
for i in range(100):
list_thread.append(i)
print("插入的資料為:", i)
lock.release()
def func2():
lock.acquire()
for i in range(100):
num = list_thread[-1]
list_thread.pop(i)
print("刪除的資料為:", num)
lock.release()
def main():
t1 = Thread(target=func1)
t2 = Thread(target=func2)
if __name__ == '__main__':
main()
第二種情況
from threading import Thread
from threading import Lock
from queue import Queue
# 定義全域性變數
list_thread = []
lock = Lock()
def func1():
# lock.acquire()
for i in range(100):
lock.acquire()
list_thread.append(i)
lock.release()
print("插入的資料為:", i)
# lock.release()
def func2():
# lock.acquire()
for i in range(100):
lock.acquire()
num = list_thread[-1]
list_thread.pop(i)
lock.release()
print("刪除的資料為:", num)
# lock.release()
def main():
t1 = Thread(target=func1)
t2 = Thread(target=func2)
if __name__ == '__main__':
main()
但是python裡面有個好東西可以解決加鎖的問題,queue。
簡單的來說就是多執行緒需要加鎖,很可能會造成死鎖,而queue自帶鎖。所以多執行緒結合queue會好的很多
queue可以傳遞多執行緒的結果,用以代替return
import threading
import time;
from queue import Queue
def job(vec,res):
for i in range(len(vec)):
vec[i] = vec[i]*2
res.put(vec)
def main():
res = Queue()
data=[[1,2,3],[3,4,5],[2,2,2],[3,3,3]]
for i in range(len(data)):
t=threading.Thread(target=job,args=(data[i],res))
t.start()
t.join()
result =[]
for j in range(len(data)):
result.append(res.get())
print(result)
if __name__ == '__main__':
main()
[[2, 4, 6], [6, 8, 10], [4, 4, 4], [6, 6, 6]]