int count = 100;
count--;
//等价于
mov eax, count
sub count,1
mov count,eax
sub count,1
的时候,就让出cpu,没将值返回内存,导致第二个线程也是从100开始减的,两个线程执行的--
操作最后的结果只有99
,这种情况就是竞态条件
,多线程执行的结果是一致的,不会随着cpu对线程不同的调用顺序,而产生不同的运行结果初始化和销毁
#include
// 初始化方法一,直接给互斥体变量赋值
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
// 初始化方法二,通过初始化函数初始化
int pthread_mutex_init(pthread_mutex_t* restrict mutex,
const pthread_mutexattr_t* restrict attr);
- 函数成功返回0,失败返回一个错误码
- 第二个参数用来获取互斥体属性,默认设置为NULL
//销毁通过初始化函数初始化的互斥体对象
int pthread_mutex_destroy(pthread_mutex_t* mutex);
- 函数执行成功返回0,失败返回一个错误码
加锁解锁操作
// 加锁
int pthread_mutex_lock(pthread_mutex_t* mutex);
//尝试加锁,没加锁就加锁,被锁住了,就返回错误码EBUSY,不会被阻塞,
int pthread_mutex_trylock(pthread_mutex_t* mutex);
// 解锁
int pthread_mutex_unlock(pthread_mutex_t* mutex);
// 上面的三个函数,成功返回0,失败返回一个错误码,表示失败原因
互斥体属性设置:
// 初始化属性变量attr
int pthread_mutexattr_init(pthread_mutexattr_t* attr);
// 设置 attr 属性类型type
int pthread_mutexattr_settype(pthread_mutexattr_t* attr, int type);
// 获取attr属性类型type
int pthread_mutexattr_gettype(const pthread_mutexattr_t* restrict attr,
int* restrict type);
// 销毁attr属性变量
int pthread_mutexattr_destroy(pthread_mutexattr_t* attr);
mutex的属性:
PTHREAD_MUTEX_NORMAL
:普通锁,mutex的默认属性,等价于pthread_mutex_init
的第二个参数设置为NULL
, 相同线程重复加锁阻塞;PTHREAD_MUTEX_ERRORCHECK
:检错锁,相同线程重复加锁,会返回EDEADLK
错误码;PTHREAD_MUTEX_ERRORCHECK
:嵌套锁,允许同一个线程进行重复加锁,没加锁一次,互斥体对象的引用计数加1,加几次锁,就需要解锁几次,其他线程才能获取找个锁mutex基本使用(普通锁):
#include
#include
#include
#include
pthread_mutex_t mtx; //初始化全局互斥体变量
int resourceNo = 0; // 全局整型变量
void* worker_thread(void* param)
{
pthread_t threadID = pthread_self();// 线程id
printf("thread start, ThreadID: %d\n", threadID);
while (true)
{
pthread_mutex_lock(&mtx); //加锁
printf("Mutex lock, resourceNo: %d, ThreadID: %d\n"
, resourceNo, threadID);
resourceNo++;
printf("Mutex unlock, resourceNo: %d, ThreadID: %d\n"
, resourceNo, threadID);
pthread_mutex_unlock(&mtx); //解锁
//休眠1秒
sleep(1);
}
return NULL;
}
int main()
{
pthread_mutexattr_t mutex_attr; //定义mutex属性变量
pthread_mutexattr_init(&mutex_attr); //初始化属性变量
//设置属性
pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_NORMAL);
pthread_mutex_init(&mtx, &mutex_attr); // 初始化mutex,并设置对应属性
//创建5个工作线程
pthread_t threadID[5];
for (int i = 0; i < 5; ++i)
{
//创建线程,等待调度
pthread_create(&threadID[i], NULL, worker_thread, NULL);
}
for (int i = 0; i < 5; ++i)
{
pthread_join(threadID[i], NULL); //等待回收线程
}
pthread_mutex_destroy(&mtx); //销毁mutex互斥体
pthread_mutexattr_destroy(&mutex_attr); //销毁
return 0;
}
TIP:
PTHREAD_MUTEX_INITIALIZER
初始化的互斥量无须销毁;EBUSY
错误码初始化和销毁
#include
// 定义条件变量对象
pthread_cond_t cond;
// 初始化条件变量
int pthread_cond_init(pthread_cond_t* cond,
const pthread_condattr_t* attr);
// 销毁条件变量
int pthread_cond_destroy(pthread_cond_t* cond);
等待条件变量
// 阻塞等待,释放锁,如果条件满足了被唤醒,会直接获得锁
int pthread_cond_wait(pthread_cond_t* restrict cond,
pthread_mutex_t* restrict mutex);
// 超时等待
int pthread_cond_timedwait(pthread_cond_t* restrict cond,
pthread_mutex_t* restrict mutex,
const struct timespec* restrict abstime);
条件唤醒
// 一次唤醒一个,随机唤醒
int pthread_cond_signal(pthread_cond_t* cond);
// 唤醒所有线程
int pthread_cond_broadcast(pthread_cond_t* cond);
TIP:
pthread_cond_wait
阻塞的线程,所以一般需要配合whlie循环和pthread_cond_wait
;pthread_cond_wait
,则可能导致信号丢失,并永久等待,因此一般需要在pthread_cond_signal或pthread_cond_broadcast
之前调用pthread_cond_wait底层是不同的操作系统的具体实现,可用于跨平台
互斥锁mutex
头文件及基本使用:用于保证多线程中临界资源的使用的串行
#include
std::mutex mtx; // 定义的全局的互斥锁
mtx.lock();//
/*
临界资源
*/
mtx.unlock();//解锁
裸的mutex对象的使用,存在一个问题:就是代码段中间存在return
,会导致不能执行到mtx.unlock()
解锁,继而导致死锁问题
相同线程重复lock
, linux上会阻塞,windows上程序会崩溃
因此有了lock_gaurd
和 unique_lock
两个对象对mutex
进行封装,类似于智能指针,构造加锁,出作用域析构自动解锁
lock_gaurd
:对mutex对象可以进行简单的封装,构造加锁,析构结构
unique_lock
:不仅对mutex对象 进行简单的封装,还提供了一系列的成员函数,比如加锁解锁等操作
两个对象的差别:
lock_gaurd
左值引用的赋值和拷贝全部delete,只能用于简单的临界区加锁解锁中,不可能用在函数参数传递,或者返回过程中,函数参数传递和返回过程中,都需要用到拷贝构造或者赋值函数unique_lock
左值引用的赋值和拷贝全部delete,但是提供了右值拷贝和右值赋值运算重载,不仅可以使用在简单的临界区互斥的代码上,还可以函数调用过程中使用,此外还这个对象还提供了lock和unlock的成员方法基本使用:
int ticketcount = 100; //车站有100张车票,由三个窗口一起卖
std::mutex mtx; //全局锁
void sellTicket(int index)
{
while (ticketcount > 0)
{
mtx.lock();
/*加锁, 当ticketCount为1的时候,临界区可能有线程在操作,而这里也可能
有线程在阻塞,最后导致ticketCount为-1, 所以需要在锁里面再进行判断
,也就是锁+双重判断*/
if (ticketcount > 0) //第二重判断
{
std::cout << "窗口:" << index << " 卖出第"
<< ticketcount << " 张票" << std::endl;
ticketcount--;
}
mtx.unlock();
//睡眠100ms
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void sellTicket2(int index)
{
while (ticketcount > 0)
{
{
//std::lock_guard lock(mtx);
std::unique_lock<std::mutex> lock(mtx);
if (ticketcount > 0) //第二重判断
{
std::cout << "窗口:" << index << " 卖出第"
<< ticketcount << " 张票" << std::endl;
ticketcount--;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(100)); //睡眠100ms
}
}
int main()
{
std::list<std::thread> t_list;
for (int i = 0; i < 3; ++i)
{
t_list.push_back(std::thread(sellTicket,i));
}
for (std::thread& t : t_list)
{
t.join();
}
std::cout << "所有窗口卖票结束" << std::endl;
return 0;
}
条件变量condition_variable
头文件和基本使用,基本和linux下的一致
#include
#include
std::mutex mtx;
std::condition_variable cv;
class Queue
{
public:
void put(int val) //生产
{
std::lock_guard<std::mutex> guard(mtx);
std::unique_lock<std::mutex> lck(mtx);
while (!que.empty())
{
cv.wait(lck); //进入等待状态就会将这把锁释放掉,必须是unique_lock
}
que.push(val);
/*
notify_one:通知一个线程
notify_all:通知所有线程
*/
cv.notify_all();
std::cout << "生产者 生产:" << val << "号物品" << std::endl;
}
int get() //消费
{
//std::lock_guard guard(mtx);
std::unique_lock<std::mutex> lck(mtx);
while (que.empty())
{
//消费者线程发现que是空的,通知生产者线程先生产物品才能消费
// 先进入等待状态,再把mutex释放掉
cv.wait(lck); //进入等待状态就会将这把锁释放掉
}
int val = que.front();
que.pop();
cv.notify_all(); //通知其他线程消费完了
std::cout << "消费者 消费:" << val << "号物品" << std::endl;
return val;
}
private:
std::queue<int> que;
};
void producer(Queue* que) //生产者线程
{
for (int i = 1; i <= 10; ++i)
{
que->put(i);
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
}
void comsumer(Queue* que) // 消费者线程
{
for (int i = 1; i <= 10; ++i)
{
que->get();
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
}
int main()
{
Queue que; //两个线程共享的队列
std::thread t1(producer,&que);
std::thread t2(comsumer,&que);
t1.join();
t2.join();
return 0;
}