数据竞争
同一进程的多个线程共享地址空间和系统资源,由此可能出现数据竞争的问题。 数据竞争举例# include <iostream>
# include <thread>
int main ( )
{
while ( true ) {
std:: thread t1 ( [ ] ( ) {
std:: cout << "1234567890" << "1234567890" << std:: endl;
} ) ;
std:: thread t2 ( [ ] ( ) {
std:: cout << "abcdefghijk" << "abcdefghijk" << std:: endl;
} ) ;
t1. join ( ) ;
t2. join ( ) ;
if ( getchar ( ) == 'n' )
break ;
}
return 0 ;
}
abcdefghijkabcdefghijk
12345678901234567890
1234567890abcdefghijkabcdefghijk
1234567890
abcdefghijkabcdefghijk12345678901234567890
究其原因,两个子线程处于并行执行的状态,却都使用了相同的输出流对象,因此发生了数据竞争。
锁机制
如何解决数据竞争问题呢? 从根本上来说,发生数据竞争是因为多个线程同时使用共享资源,因此为了解决这个问题,可以避免多个线程同时使用共享资源。
使用互斥锁
当互斥量(mutex)被某个线程锁住时,其他尝试获取该互斥量的线程会进入阻塞状态,直到该互斥量被释放为止。但这些线程仍然可以执行其他与mutex无关的代码,即它们不会完全停止执行,只是无法进入被mutex保护的临界区。# include <iostream>
# include <thread>
# include <mutex>
int main ( )
{
std:: mutex mtx;
while ( true ) {
std:: thread t1 ( [ & ] ( ) {
mtx. lock ( ) ;
std:: cout << "1234567890" << "1234567890" << std:: endl;
mtx. unlock ( ) ;
} ) ;
std:: thread t2 ( [ & ] ( ) {
mtx. lock ( ) ;
std:: cout << "abcdefghijk" << "abcdefghijk" << std:: endl;
mtx. unlock ( ) ;
} ) ;
t1. join ( ) ;
t2. join ( ) ;
if ( getchar ( ) == 'n' )
break ;
}
return 0 ;
}
使用互斥锁后,只可能出现以下两种结果,即同一线程内的输出顺序正常。 12345678901234567890
abcdefghijkabcdefghijk
abcdefghijkabcdefghijk
12345678901234567890
使用std::lock_guard
实际使用互斥锁的过程中,常常忘记unlock
,为了解决这个问题,C++提供了RAII风格的锁包装器std::lock_guard
,它在构造时自动锁定给定的互斥量,并在析构时自动解锁。这有助于确保互斥量在异常情况下也能被正确解锁。
# include <iostream>
# include <thread>
# include <mutex>
int main ( )
{
std:: mutex mtx;
while ( true ) {
std:: thread t1 ( [ & ] ( ) {
std:: lock_guard< std:: mutex> lg ( mtx) ;
std:: cout << "1234567890" << "1234567890" << std:: endl;
} ) ;
std:: thread t2 ( [ & ] ( ) {
mtx. lock ( ) ;
std:: lock_guard< std:: mutex> lg ( mtx, std:: adopt_lock) ;
std:: cout << "abcdefghijk" << "abcdefghijk" << std:: endl;
} ) ;
t1. join ( ) ;
t2. join ( ) ;
if ( getchar ( ) == 'n' )
break ;
}
return 0 ;
}
使用std::unique_lock
std::unique_lock
对象构造时会对互斥量加锁,析构时解锁。此外还能对互斥量进行更灵活的管理,比如延迟加锁。可以用std::unique_lock
替换std::lock_guard
,构造时不加锁:std::unique_lock<std::mutex> ul(mtx, std::defer_lock);
。支持手动lock和unlock 延迟加锁
原子操作