在 Linux 下,可以通过多种方式保证一个线程函数在同一时间只能运行一个实例(即线程安全)。以下是几种常见的方法:
1. 使用互斥锁(Mutex)
最常用的方法,确保同一时间只有一个线程能进入临界区。
示例代码(C++11 <mutex>
)
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 全局互斥锁
void thread_func() {
std::lock_guard<std::mutex> lock(mtx); // 自动加锁,离开作用域解锁
std::cout << "Thread ID: " << std::this_thread::get_id() << " is running." << std::endl;
// ... 临界区代码 ...
}
int main() {
std::thread t1(thread_func);
std::thread t2(thread_func);
t1.join();
t2.join();
return 0;
}
std::lock_guard
:RAII机制,防止忘记解锁。
std::unique_lock
:更灵活,可以手动控制加解锁。
2. 使用原子变量(Atomic Flag)
适用于简单场景,避免锁的开销。
示例代码
#include <atomic>
#include <thread>
std::atomic_flag flag = ATOMIC_FLAG_INIT;
void thread_func() {
if (flag.test_and_set()) { // 检查是否已被占用
return; // 如果已经在运行则退出
}
// ... 临界区代码 ...
flag.clear(); // 释放标志位
}
- 适用场景:轻量级同步需求。
3. POSIX 信号量(Semaphore)
适用于跨进程或更复杂的同步控制。
示例代码
#include <semaphore.h>
#include <pthread.h>
sem_t sem;
void* thread_func(void*) {
sem_wait(&sem); // P操作(获取信号量)
// ... 临界区代码 ...
sem_post(&sem); // V操作(释放信号量)
}
int main() {
sem_init(&sem, 0, 1); // 初始值为1(二进制信号量)
pthread_t t1, t2;
pthread_create(&t1, NULL, thread_func, NULL);
pthread_create(&t2, NULL, thread_func, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
sem_destroy(&sem);
}
4. C++11 call_once
确保某个函数在多线程环境下只执行一次。
示例代码
#include <mutex>
#include <thread>
std::once_flag flag;
void init_function() {
std::cout << "This runs only once!" << std::endl;
}
void thread_func() {
std::call_once(flag, init_function);
}
- 适用场景:单例初始化等。
5. Linux Futex(快速用户态互斥锁)
高性能场景下的底层同步机制,如 Nginx、Redis。
示例伪代码
// (需结合汇编或系统调用实现)
总结
方法 | 适用场景 | 特点 |
---|---|---|
Mutex | 通用多线程同步 | C++标准库支持 |
Atomic Flag | 轻量级标志位检查 | 无锁但功能有限 |
Semaphore | 跨进程/复杂同步 | POSIX标准 |
call_once | 单次初始化 | C++11特性 |
Futex | 高性能底层开发 | Linux特有 |
选择合适的方式取决于具体需求:
- 推荐默认用
std::mutex
(简单可靠)。
- 低延迟场景考虑原子变量或无锁结构。
- 跨进程用信号量或共享内存+互斥锁。
如果需要进一步优化性能,可研究自旋锁(pthread_spinlock_t
)或读写锁(std::shared_mutex
)。
内容由零声教学AI助手提供,问题来源于学员提问