开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 11 天,点击查看活动详情
系统线程函数
pthread_create
创建线程,确定线程的入口函数,返回值0表示成功,非0表示出错
- Linux、Mac
#include <pthread.h>
int pthread_create(
pthread_t *restrict tidp, //新创建的线程ID指向的内存单元。
const pthread_attr_t *restrict attr, //线程属性,默认为NULL
void *(*start_rtn)(void *), //新创建的线程从start_rtn函数的地址开始运行
void *restrict arg //默认为NULL。若上述函数需要参数,将参数放入结构中并将地址作为arg传入。
);
- Windows
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 定义新线程的安全属性,一般为NULL
DWORD dwStackSize,// 分配线程堆栈的大小,默认值为0
LPTHREAD_START_ROUTINE lpStartAddress,// 线程函数地址
LPVOID lpParameter,// 传递给线程函数的参数
DWORD dwCreationFlags,// 创建线程的运行状态,CREATE_SUSPEND表示挂起当前创建的线程,0表示立即执行当前创建的进程
LPDWORD lpThreadID// 返回新创建线程的ID
);
pthread_mutex_init
初始化互斥锁,互斥锁可以是线程按顺序执行,
- Linux、Mac
int pthread_mutex_init(
pthread_mutex_t * restrict mutex,
const pthread_mutexattr_t * restrict attr // 互斥锁属性,如果为null,则使用默认的快速互斥锁
);
- Windows
找出当前系统中是否存在指定进程的实例,如果没有,则创建一个互斥体
执行成功就返回互斥体的句柄
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, // 指向安全属性的指针
BOOL bInitialOwner, // 初始化互斥对象的所有者
LPCTSTR lpName // 指向互斥对象名的指针
);
STL Thread
std::thread
#include <stdio.h>
#include <iostream>
#include <thread>
using namespace std;
int n = 0;
void count(int num){
for (int i = 0; i < num; i++)
{
n++;
}
}
int main(int, char**){
thread t1(count, 100000);
thread t2(count, 100000);
t2.join(); // 等待线程结束,会阻塞
t1.join();
cout << n << endl;
system("pause");
return 0;
}
没有执行join
或detach
的线程在程序结束时会引发异常
运行结果:117095,明显小于200000,要解决这个问题,就需要用到互斥锁
std::mutex
互斥量(mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁以后,任何其他试图再次对互斥锁加锁的线程将会阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为运行状态的线程可以对互斥锁加锁,其他线程将会看到互斥锁依然被锁住,只能回去再次等待它重新变为可用。
#include <stdio.h>
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
int n = 0;
std::mutex mtx;
void count(int num){
for (int i = 0; i < num; i++)
{
mtx.lock();
n++;
mtx.unlock();
}
}
int main(int, char**){
thread t1(count, 100000);
thread t2(count, 100000);
t2.join();
t1.join();
cout << n << endl;
system("pause");
return 0;
}
std::atomic
因为频繁的lock、unlock会有一定的性能影响,atomic就是在解决这个问题,只需要变量类型为automic_int即可,其他类型同理:
atomic_int n;
std::lock_guard
guard(警卫,看守)
锁管理器在构造函数中自动绑定它的互斥体并加锁,在析构函数中解锁,大大减少了死锁的风险
template <class _Mutex>
class _NODISCARD lock_guard { // class with destructor that unlocks a mutex
public:
using mutex_type = _Mutex;
explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) { // construct and lock
_MyMutex.lock();// 构造加锁
}
lock_guard(_Mutex& _Mtx, adopt_lock_t) : _MyMutex(_Mtx) {} // construct but don't lock
~lock_guard() noexcept {
_MyMutex.unlock(); // 析构解锁
}
lock_guard(const lock_guard&) = delete;
lock_guard& operator=(const lock_guard&) = delete;
private:
_Mutex& _MyMutex;
};
std::unique_lock
unique_lock 是为了避免 mutex 忘记释放锁。 在对象创建时自动加锁,对象释放时自动解锁。 和lock_guard不同的是,unique_lock支持随时lock,unlock。
std::unique_lock<mutex> lock(m_Mutex);
std::condition_variable
// thread1
while(true)
{
pthread_mutex_lock(&mutex);
iCount++;
pthread_mutex_unlock(&mutex);
}
//thread 2:
while(true)
{
pthread_mutex_lock(&mutex);
if(iCount >= 100)
{
iCount = 0;
}
pthread_mutex_unlock(&mutex);
}
这种实现下,就算 lock 空闲,thread2需要不断重复<加锁,判断,解锁>这个流程,会给系统带来不必要的开销。有没有一种办法让 thread2先被 block,等条件满足的时候再唤醒 thread2?这样 thread2 就不用不断进行重复的加解锁操作了?这就要用到条件变量了
执行notify_one
或者notify_all
去唤醒阻塞线程。