导航
线程安全
所谓线程安全不是指线程的安全,而是指内存的安全。线程是由进程所承载,所有线程均可访问进程的上下文,意味着所有线程均可访问在进程中的内存空间,这也是线程之间造成问题的潜在原因。当多个线程读取同一片内存空间(变量、对象等)时,不会引起线程安全问题。但是当多个线程对同一片内存空间进行写操作时,就需要考虑内存安全问题。
线程不安全例子
#include <iostream>
#include <thread>
using namespace std;
int counter = 0;
void func()
{
for (int i = 0; i < 10000; ++i)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1)); // 休眠1ms
++counter; // 写入操作:counter自加
}
}
int main()
{
std::thread t1 = std::thread(func);
std::thread t2 = std::thread(func);
t1.join();
t2.join();
cout << "counter: " << counter << endl;
return 0;
}
上述代码,预期结果是counter输出20000,因为两个线程均会进入循环,counter各自自加10000次。实际输出效果如下:
counter: 19989
counter: 19992
两次输出结果均不相同,且均不等于预期的20000。这就线程不安全导致的BUG。
线程锁
为了解决上述线程安全问题,就需要线程锁,C和C++最常用的锁包括互斥锁、条件锁、自旋锁和读写锁等。其中最基本的是互斥锁,其他的锁都是基于互斥锁实现。所以锁的功能越强大,性能就越低。
互斥锁
互斥锁(mutex),也成为互斥量,就是保证多线程共享同一个互斥量,然后让线程之间。当一个线程对共享数据操作时,利用互斥锁进行上锁,其他线程在尝试再次上锁时,发现已上锁就会睡眠等待。等到互斥锁解开,才能再次上锁进而继续操作,从而保护共享数据的安全。下面利用互斥锁,解决上述BUG。
C++线程安全实现
#include <iostream>
#include <thread>
#include <mutex>
int counter = 0;
std::mutex mutex;
void func()
{
for (int i = 0; i < 10000; ++i)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
mutex.lock();
++counter;
mutex.unlock();
}
}
int main()
{
std::thread t1 = std::thread(func);
std::thread t2 = std::thread(func);
t1.join();
t2.join();
std::cout << "counter: " << counter << std::endl;
return 0;
}
输出结果如下:
counter: 20000
counter: 20000
两次运行输出均为20000,与预期结果相同,证明加入互斥锁后解决线程安全问题。
C语言线程安全实现
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int counter = 0;
pthread_mutex_t mutex;
void *func(void *val)
{
for (int i = 0; i < 10000; ++i)
{
usleep(1 * 1000);
pthread_mutex_lock(&mutex);
++counter;
pthread_mutex_unlock(&mutex);
}
}
int main()
{
pthread_mutex_init(&mutex, NULL);
pthread_t t1;
pthread_t t2;
pthread_create(&t1, NULL, func, NULL);
pthread_create(&t2, NULL, func, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_mutex_destroy(&mutex);
printf("counter: %d\n", counter);
return 0;
}
输出如下:
counter: 20000
输出也是预期的20000。