线程同步用于管理线程运行的顺序。当每个线程只运行一次时,线程同步用于管理各线程按顺序运行,称为有序同步。当线程内有循环体时,相当于一个线程运行多次,有无序同步和有序同步两种需求。无序同步:所有线程都执行完一次循环体再进行下一次循环,每次执行时线程的顺序任意。有序同步:所有线程按顺序都执行一次循环体再进行下一次循环。
1. 有序同步
16 个线程按顺序输出一句话。重复 5 次这样的顺序输出。
1. 信号量
class Sync {
public:
Sync(size_t x = 1) :thread_num(x), con(0) {}
void lock(int tid) {
unique_lock<mutex> locker(mx);
cv.wait(locker, [&]() {return con == tid; });
}
void info() {
if (con == thread_num - 1)
cout << "-- 完成一轮。" << endl;
con = (con + 1) % thread_num;
// lock(con);
cv.notify_all();
}
private:
size_t thread_num;
condition_variable cv;
mutex mx;
int con;
};
令条件 con 为 n 则线程 n 运行。
第 11、13 行:调用 info 函数的线程已经执行完毕,在它执行时可能有很多线程去等待条件,第 11 行设置一个条件,第 13 行让满足条件的线程获得锁,不满足条件的线程也不再继续等待条件。第 12 行确保满足条件的线程在等待条件。
2. 线程函数
void kernel(const int tid, Sync& sync) {
for (int i = 0; i < 5; i++) {
sync.lock(tid);
cout << "<" << tid << ">" << endl;
sync.info();
}
}
3. 测试
int main() {
const int thread_num = 16;
Sync sync(thread_num);
vector<thread> pool;
for (int tid = 0; tid < thread_num; tid++) {
pool.push_back(thread(kernel, tid, ref(sync)));
//Sleep(20);
}
for (int i = 0; i < thread_num; i++)
pool[i].join();
system("pause");
}
4. 头文件
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
using namespace std;
2. 无序同步
16 个线程各输出一句话。重复 5 次这样的输出。
应用场景:树状加法、读写操作。下面使用条件变量实现无序同步。
1. 信号量
class Sync {
public:
Sync(size_t x = 1) :thread_num(x), wait_num(0) {
mxs = new mutex[thread_num];
con = new bool[thread_num];
memset(con, 0, sizeof(bool) * thread_num);
}
void lock(int i) {
unique_lock<mutex> locker(mxs[i]);
cv.wait(locker, [&]() {return !con[i]; });
con[i] = true;
}
void info() {
wait_num++;
if (wait_num == thread_num) {
cout << "-- 完成一轮。" << endl;
memset(con, 0, sizeof(bool) * thread_num);
wait_num = 0;
cv.notify_all();
}
}
~Sync() {
delete[] mxs;
delete[] con;
mxs = nullptr;
con = nullptr;
}
private:
size_t thread_num;
condition_variable cv;
mutex* mxs;
bool* con;
atomic<int> wait_num;
};
Sync 这个类的功能相当于信号量。条件变量和互斥锁、条件配合使用,所以 mxs 是各线程的互斥锁,con 是各互斥锁的条件。我们让每个线程有一个互斥锁,目的是控制它什么时候运行,什么时候等待。
有互斥锁,为什么还使用条件变量:一个进程第一次申请锁没有释放,第二次再申请那个锁就会引起程序崩溃。而使用条件变量可以在申请锁前等待条件,避免直接申请引起崩溃,如第 10 行。如果条件满足,wait 函数会原子地 unlock 锁然后再 lock 锁。
2. 线程函数
void kernel(const int tid, Sync& sync) {
for (int i = 0; i < 5; i++) {
sync.lock(tid);
cout << "<" << tid << ">" << endl;
sync.info();
}
}
3. 测试
int main() {
const int thread_num = 16;
Sync sync(thread_num);
vector<thread> pool;
for (int tid =0; tid < thread_num; tid++) {
pool.push_back(thread(kernel, tid, ref(sync)));
//Sleep(20);
}
for (int i = 0; i < thread_num; i++)
pool[i].join();
system("pause");
}
thread 类的拷贝构造函数是删除的,所以要在 vector 的 push_back 中创建线程,而不能把创建的线程对象 push_back 到 vector。或者通过移动构造函数把线程对象 push_back 到 vector。
4. 头文件
#include <iostream>
#include <mutex>
#include <atomic>
#include <thread>
#include <vector>
using namespace std;