#include#include #include using namespace std; void thread01() { for (int i = 0; i < 5; i++) { cout << "Thread 01 is working !" << endl; Sleep(100); } } void thread02() { for (int i = 0; i < 5; i++) { cout << "Thread 02 is working !" << endl; Sleep(200); } } int main() { thread task01(thread01); //创建一个thread类对象task01,以thread01做为一个线程创建时构造的参数,创建的线程01执行的是thread01函数中的任务 thread task02(thread02); task01.join(); //将01子线程加入,阻塞主线程,等到01运行完才进行主线程 task02.join(); //将02子线程加入,阻塞主线程,等到02运行完才进行主线程 for (int i = 0; i < 5; i++) { cout << "Main thread is working !" << endl; Sleep(200); } system("pause"); }
子线程01,02不会互相阻塞对方,当这两个子线程都结束时才会继续主线程
#include#include #include using namespace std; int a = 0; void thread01() { for (int i = 0; i < 10000000; i++) a++; } int main() { thread task01(thread01);//以thread01做为一个线程创建时的参数,创建的线程01执行的是thread01函数中的任务 for (int i = 0; i < 10000000; i++) a++; task01.join();//将01子线程加入,阻塞主线程,等到01运行完才进行主线程 cout << a << endl; system("pause"); }
将i 设置到10000000时会出现错误结果,一次for循环的时间大于两条语句之间的间隔,可以看到,代码中全局变量i先在主线程中自加了10000000次,再在主线程中自加了10000000次,结果应是20000000,但显示的结果为13010045,推测是主线程进行了3010045次还未结束时子线程就加进来了阻塞了主线程然后执行子线程,于是将子进程设置为空函数,预计最后显示的结果为3010045
可以看到结果并不是我们的猜想,说明子进程的加入并不会阻塞正在进行的主线程中的 *** 作
我们查看子进程中a++的反汇编:
a++; 00D02643 mov eax,dword ptr [a (0D0E2D4h)] //将内存中地址为0D0E2D4h的值mov给寄存器eax 00D02648 add eax,1 //将eaxadd1 00D0264B mov dword ptr [a (0D0E2D4h)],eax //将eax的值放回 0D0E2D4h中 00D02650 jmp thread01+31h (0D02631h)
C++为高级语言,一句自增 *** 作在翻译成汇编语言后变成了四句
再查看主进程中a++的反汇编:
00D03C39 mov eax,dword ptr [a (0D0E2D4h)] 00D03C3E add eax,1 00D03C41 mov dword ptr [a (0D0E2D4h)],eax 00D03C46 jmp std::thread::join+47h (0D03C27h)
可以看到a存放的寄存器的地址相同,但指令存放的地址不同。
并发为交替地进行两个以下的 *** 作,假设每个 *** 作执行X毫秒
当子进程加进来后,比如在执行进程1X毫秒后只执行了第一句,假设此时a为5000,然后去执行进程2执行X毫秒a自增了3000,再切换回进程1接着执行之前没有执行完的第二局,a变回5000,在进程2中自增的3000消失掉了。
这就是同步带来的问题
原子 *** 作:是指线程在访问资源时确保其他线程不会访问相同的资源(让C++一行代码对应的多行汇编代码视作一个整体,要么一起执行完毕,要不一行都不执行)
for (int i = 0; i < 10000000; i++) { InterlockedAdd((long*)&a, 1); }
但这仅限于加法,泛化性不高
因此引入锁这个概念
使用了windows的API #include
在使用前先创建临界区对象,相当于去买锁
CRITICAL_SECTION g_cs;
要先初始化锁InitializeCriticalSection,使用结束了要删掉锁DeleteCriticalSection,使用锁前EnterCriticalSection加锁,使用后解锁LeaveCriticalSection;
#include4.颗粒度#include #include using namespace std; int a = 0; //创建临界区对象--等价于锁 CRITICAL_SECTION g_cs; void thread01() { for (int i = 0; i < 10000000; i++) { //进来时上锁 EnterCriticalSection(&g_cs); a++; //出去解锁 LeaveCriticalSection(&g_cs); } } int main() { InitializeCriticalSection(&g_cs); thread task01(thread01); for (int i = 0; i < 10000000; i++) { //进来时上锁 EnterCriticalSection(&g_cs); a++; //出去解锁 LeaveCriticalSection(&g_cs); } task01.join(); cout << a << endl; system("pause"); //不使用时删掉该锁 DeleteCriticalSection(&g_cs); }
上锁方法1
for (int i = 0; i < 10000000; i++) { EnterCriticalSection(&g_cs); a++; LeaveCriticalSection(&g_cs); }
上锁方法2
EnterCriticalSection(&g_cs); for (int i = 0; i < 10000000; i++) { a++;} LeaveCriticalSection(&g_cs);
1的颗粒度小于2,尽量让颗粒度小,不然其他不会影响该进程的进程就无法加入,浪费资源
5.自己封装线程同步锁手动上锁的话可能会忘了解锁,如果没有解锁LeaveCriticalSection的话会一直卡在这个进程里,其他进程想进入这个锁的房间进不去(比喻),因此自己封装线程同步锁,新建一个mutex类,将windows的api封装进去
mutex.h
#includeclass mutex { public: mutex(); ~mutex(); void lock(); void unlock(); private: CRITICAL_SECTION g_cs; };
mutex.cpp
#include "mutex.h" mutex::mutex() { InitializeCriticalSection(&g_cs); } mutex::~mutex() { DeleteCriticalSection(&g_cs); } void mutex::lock() { EnterCriticalSection(&g_cs); } void mutex::unlock() { LeaveCriticalSection(&g_cs); }
main.cpp
#include#include #include #include"mutex.h" using namespace std; int a = 0; CRITICAL_SECTION g_cs; mutex mt; void thread01() { for (int i = 0; i < 10000000; i++) { mt.lock(); a++; mt.unlock(); } } int main() { thread task01(thread01); for (int i = 0; i < 10000000; i++) { mt.lock(); a++; mt.unlock(); } task01.join(); cout << a << endl; system("pause"); DeleteCriticalSection(&g_cs); }
此时还没有解决可能遗忘解锁的问题
#include#include #include #include"mutex.h" using namespace std; int a = 0; mutex mt;
将windows的api封装起来在mt中,使用前对该对象进行初始化,在mt的构造函数中初始化锁,析构函数中删除锁
CRITICAL_SECTION g_cs;
创建临界区对象g_cs–等价于锁
class mutexguard { public: mutexguard() { mt.lock(); } ~mutexguard() { mt.unlock(); } }; ```cpp #include "mutex.h" mutex::mutex() { InitializeCriticalSection(&g_cs); } mutex::~mutex() { DeleteCriticalSection(&g_cs); } void mutex::lock() { EnterCriticalSection(&g_cs); } void mutex::unlock() { LeaveCriticalSection(&g_cs); }
新建一个类,通过该类的构造函数来调用mt的上锁 *** 作
通过该类的自动析构来自动调用mt的解锁 *** 作
```cpp void thread01() { { mutexguard mg2; for (int i = 0; i < 10000000; i++) a++; } } int main() { thread task01(thread01); { mutexguard mg1; for (int i = 0; i < 10000000; i++) a++; } task01.join(); cout << "a:"<
其中:
{
mutexguard mg2;
for (int i = 0; i < 10000000; i++)
a++;
}
将mg2的生命周期规定了在括号内,减少颗粒度
可以看到结果正常
#include#include #include #include using namespace std; int a = 0; mutex lock1; void thread01() { { lock1.lock(); for (int i = 0; i < 10000000; i++) a++; lock1.unlock(); } } int main() { thread task01(thread01); { lock1.lock(); for (int i = 0; i < 10000000; i++) a++; lock1.unlock(); } task01.join(); cout << "a:" << a << endl; system("pause"); }
也可以用stl里自带的模板来封装该锁来自动删除锁
void thread01() { { lock_guard7.死锁产生原因lg(lock1); for (int i = 0; i < 10000000; i++) a++; } }
死锁:线程在等待一个永远不会成立的条件成立,从而陷入无尽的等待,永远不能被唤醒的状态叫死锁。
#include#include #include #include using namespace std; mutex m1; mutex m2; void f1() { lock_guard lg1(m1); cout << "进程1获得锁1" << endl; Sleep(2000);//确保f2能获得锁2 lock_guard lg2(m2); cout << "进程1获得锁2" << endl; } void f2() { Sleep(1000);//确保f1能获得锁1 lock_guard lg3(m2); cout << "进程2获得锁2" << endl; lock_guard lg4(m1); cout << "进程2获得锁1" << endl; } void main() { thread task01(f1); thread task02(f2); task01.join(); task02.join(); }
将2个线程比作2个人,一个房间有且只有唯一的锁与之对应,当A人进入房间后会将房门的锁从里面反锁上,B想进这个房间只有等A将锁打开,当A把锁打开后,如果B进入了该房间也会从里面反锁上
在等待时, *** 作系统会调度线程选择一个合适的线程去跑,只要门没有被锁就可以进,比如1000ms时线程2用m2去开门,由于此时虽然m1锁着在,但线程2此时不走m1锁着的门,而是去开m2锁着的门。
C++11提供以下4中互斥量
std::mutex 独占的互斥量,不能递归使用
std::recursive_mutex 递归的互斥量,不带超时功能
std::timed_mutex带超时功能的独占互斥量,不能递归使用
std::recursive_timed_mutex带超时功能的递归互斥量
#include#include #include #include using namespace std; void main() { recursive_mutex rm; rm.lock(); rm.lock(); cout << "111" << endl; rm.unlock(); rm.unlock(); }
recursive_mutex 递归的互斥量,相当于这个房间可以多次上锁,但走出房间时要一一把这些锁打开,但要注意多次上锁的动作主体应该是同一个人(进程)
#include#include #include #include using namespace std; timed_mutex tm; void f1() { lock_guard g1(tm); if(tm.try_lock_for(chrono::seconds(5))) cout << "222" << endl; } void main() { lock_guard g1(tm); thread t1(f1); cout << "111" << endl; t1.join(); }
timed_mutex超时锁,当有人想进入房间时会在这个锁边上等待一段时间,不会因为进不去就立马放弃,使用tm.try_lock_for(chrono::seconds(5))尝试等待5秒开锁,避免了阻塞导致的程序瘫痪。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)