目录
thread
线程的构造方式
相关成员函数
join与detach
线程传参
互斥量mutex
mutex
Locks
原子性操作库
条件变量
thread
线程的构造方式
它是不支持拷贝构造,赋值的,但是可以支持移动构造,移动赋值。还可以直接创建无参的对象。
它的有参的构造是支持可变参数模板的,
fn
:可调用对象,比如函数指针、仿函数、lambda表达式、被包装器包装后的可调用对象等。args...
:调用可调用对象fn时所需要的若干参数。
例如:
使用构造函数:
void Print1(int n)
{
for (int i = 0; i < n; i++)
{
cout << "hello" << endl;
}
}
int main()
{
int n = 1000;
thread t1(Print1, n);
thread t2(Print1, n );
t1.join();
t2.join();
return 0;
}
使用lambda:
thread t3([&]() {
for (int i = 0; i < n; i++)
{
cout << "hello" << endl;
}
}
);
t3.join();
也可以使用移动构造:
//thread t3 = t1;//不允许
thread t4 = thread(Print1, n);//移动构造
t4.join();
相关成员函数
例如:常用的函数
join:对该线程进行等待,在等待的线程返回之前,调用join函数的线程将会被阻塞
detach:将该线程与创建线程进行分离,被分离后的线程不再需要创建线程调用join函数对其进行等待。
get_id:获取该线程的id
joinable:判断该线程是否已经执行完毕,如果是则返回true,否则返回false.
joinable
函数还可以用于判定线程是否是有效的这些情况,线程都是无效的采用无参构造函数构造的线程对象。(该线程对象没有关联任何线程)
线程对象的状态已经转移给其他线程对象。(已经将线程交给其他线程对象管理)
线程已经调用join或detach结束。(线程已经结束)
但上面这些函数都是需要通过线程对象来获取的,如果要在线程对象关联的线程函数中获取线程id,可以调用this_thread
命名空间下的get_id
函数。
该空间下的函数:
yield:只让当前线程退出运行状态,让其它线程去调度。
join与detach
当一个线程退出时,需要对该线程所使用的资源进行回收,否则可能会导致内存泄露等问题。
可以是主线程调用join函数,对线程进行回收,或者也可以调用detach
函数将新线程与主线程进行分离,分离后新线程会在后台运行,其所有权和控制权将会交给C++运行库,此时C++运行库会保证当线程退出时,其相关资源能会被正确回收。
这里也存在一个问题,若在某些情况下,导致线程不能被正确回收,例如:
void Print1(int n,int& k)
{
for (int i = 0; i < n; i++)
{
k++;
}
}
void Errno(bool& flag)
{
flag = true;
}
int main()
{
int n = 100000;
int k = 0;
thread t1(Print1,n,ref(k));
thread t2(Print1,n,ref(k));
bool flag = false;
Errno(flag);
if (flag)
return -1;
t1.join();
t2.join();
cout << "k=" << k << endl;
return 0;
}
这里可以借用RAII思想解决,代码示例:
class RAII
{
public:
RAII(thread& t)
:t_(t)
{
}
~RAII()
{
if (t_.joinable())
{
t_.join();
cout << "线程回收" << endl;
}
}
private:
thread& t_;
};
int main()
{
int n = 100000;
int k = 0;
thread t1(Print1,n,ref(k));
thread t2(Print1,n,ref(k));
RAII T1(t1);
RAII T2(t2);
bool flag = false;
Errno(flag);
if (flag)
return -1;
t1.join();
t2.join();
cout << "k=" << k << endl;
return 0;
}
结果:
线程传参
线程传参时是以值拷贝的方式拷贝到线程空间当中的,即使将参数设置为引用,也不会改变外边的变量。
例如:
void Print1(int n,int& m)
{
for (int i = 0; i < n; i++)
{
cout << "hello" << endl;
m++;
}
}
int main()
{
int n = 1000;
int m = 0;
thread t1(Print1, n,m);
thread t2(Print1, n,m);
cout << m << endl;
t1.join();
t2.join();
return 0;
}
这段代码在vs2019还下不能运行
解决方法:
使用std::ref函数,当线程中函数的参数为引用时,可以借助ref函数保持对实参的引用,而不是线程栈空间中的拷贝。
例如:
thread t1(Print1, n,ref(m));
thread t2(Print1, n,ref(m));
将函数的参数改为指针类型,例如:
或者使用lambda,例如:
互斥量mutex
mutex
以这段代码为例:
void Print1(int n,int& k)
{
for (int i = 0; i < n; i++)
{
k++;
}
}
int main()
{
mutex mu_;
int n = 100000;
int k = 0;
thread t1(Print1,n,ref(k));
thread t2(Print1,n,ref(k));
t1.join();
t2.join();
cout << "k=" << k << endl;
return 0;
}
由于k++不是原子性的操作,当多个执行流去对这个变量操作时,可能会出现二义性。
结果:
对于临界资源,要进行加锁保护。C++11提供了4种锁:
都是不能进行拷贝的。不同的锁分别用于不同的场景中。最常使用的就是mutex。
该锁的成员函数有:
try_lock:尝试对该线程加锁,若成功会返回TRUE,失败返回false。
对上面的代码进行加锁:
Locks
在对代码进行加锁的过程中,可能因为加锁的代码太长,进行了误操作。或者抛异常等原因,从而导致死锁问题。这里便也可以使用RAII思想。在需要加锁的地方,创建一个Lock对象,其构造函数会进行加锁,在其生命结束时,自动调用析构函数进行解锁。
例如:
template<class Lock>
class LockGuard
{
public:
LockGuard(Lock& lk)
:_lock(lk)
{
_lock.lock();
cout << "thread:" << this_thread::get_id() << "加锁" << endl;
}
~LockGuard()
{
cout << "thread:" << this_thread::get_id() << "解锁" << endl << endl;
_lock.unlock();
}
private:
Lock& _lock;
};
c++11也提供了两个类型用来加锁,其中unique_lock还提供了一些成员函数,使用上更加灵活。
例如:
原子性操作库<atomic>
当一个数据被多个执行流访问时,除了加锁进行互斥访问外,C++11中引入了原子操作类型。
支持模板,可以定任意原子类型数据
对上面代码进行更改:
补充:定义为原子数据的变量,是不能进行赋值,拷贝构造,移动构造的。
条件变量
使用是要搭陪锁(互斥量)去使用,以第一个类为例。它们提供的成员函数可分为两大类。
一个是等待,一个是去唤醒。
等待:以wait为例
提供了两个版本:
一个是直接传入unique<mutex_>,若线程调用wait后会立即被阻塞,直到被唤醒。
另一个还需传入一个返回值为bool的对象,当线程被唤醒后还需要调用传入的可调用对象,如果可调用对象的返回值为false,那么该线程还会被继续被阻塞。
唤醒:
一个是唤醒等待队列中的第一个线程,另一个是唤醒全部线程
代码测试:以两个线程交替打印100位例,一个线程打印奇数,另一个打印偶数;
int main()
{
int i = 0;
int n = 100;
mutex mtx;
thread t1([&](){
while (i < n)
{
while (i % 2 == 0)
{
this_thread::yield();
}
cout << "t1--" << this_thread::get_id() << ":" << i << endl;
i += 1;
}
});
// t2打印偶数
thread t2([&]() {
while (i < n)
{
while (i % 2 != 0)
{
this_thread::yield();
}
cout <<"t2--"<<this_thread::get_id() << ":" << i << endl;
i += 1;
}
});
this_thread::sleep_for(chrono::seconds(3));
cout << "t1:" << t1.get_id() << endl;
cout << "t2:" << t2.get_id() << endl;
t1.join();
t2.join();
return 0;
}
使用条件变量:
int main()
{
int i = 0;
int n = 100;
mutex mtx;
condition_variable cv;
bool ready = true;
thread t1([&](){//打印奇数
while (i < n)
{
unique_lock<mutex> lock(mtx);
cv.wait(lock, [&ready]() {return !ready; });
cout << "t1--" << this_thread::get_id() << ":" << i << endl;
i += 1;
ready = true;
cv.notify_one();
//this_thread::yield();
//this_thread::sleep_for(chrono::microseconds(100));
}
});
thread t2([&]() {// t2打印偶数
while (i < n)
{
unique_lock<mutex> lock(mtx);
cv.wait(lock, [&ready](){return ready; });
cout <<"t2:"<<this_thread::get_id() << ":" << i << endl;
i += 1;
ready = false;
cv.notify_one();
}
});
this_thread::sleep_for(chrono::seconds(3));
cout << "t1:" << t1.get_id() << endl;
cout << "t2:" << t2.get_id() << endl;
t1.join();
t2.join();
return 0;
}
结果: