文章目录
- 📝线程互斥
- 🌠 库函数strncpy
- 🌉进程线程间的互斥相关背景概念
- 🌉互斥量mutex
- 🌠线程同步
- 🌉条件变量
- 🌉同步概念与竞态条件
- 🌉 条件变量函数
- 🚩总结
📝线程互斥
🌠 库函数strncpy
🌉进程线程间的互斥相关背景概念
- 临界资源:多线程执⾏流共享的资源就叫做临界资源
- 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
- 互斥:任何时刻,互斥保证有且只有⼀个执⾏流进⼊临界区,访问临界资源,通常对临界资源起保护作⽤
- 原⼦性(后⾯讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成
🌉互斥量mutex
- ⼤部分情况,线程使⽤的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程⽆法获得这种变量。
- 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。
- 多个线程并发的操作共享变量,会带来⼀些问题。
Makefile文件
bin=ticket
cc=g++
src=$(wildcard *.cc)
obj=$(src:.cc=.o)$(bin):$(obj)$(cc) -o $@ $^ -lpthread
%.o:%.cc@echo "Comiling $< to $@"$(cc) -c $< -std=c++17.PHONY:clean
clean:rm -f $(bin) $(obj).PHONY:test
test:echo $(src)echo $(obj)
代码:
#include <stdio.h>
#include <string>
#include <string.h>
#include <pthread.h>
#include <unistd.h>int ticket = 100;void* routine(void* args)
{char *id = (char*)args;// std::string id = static_cast<const char*>(args);while(1){if(ticket > 0){usleep(10000);printf("%s sells ticket:%d\n", id, ticket);ticket--;}else{break;}}return nullptr;
}int main(void)
{pthread_t t1, t2 , t3, t4;pthread_create(&t1, nullptr, routine, (void*)"thread 1");pthread_create(&t2, nullptr, routine, (void*)"thread 2");pthread_create(&t3, nullptr, routine, (void*)"thread 3");pthread_create(&t4, nullptr, routine, (void*)"thread 4");pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr);pthread_join(t4, nullptr);return 0;
}
if
语句判断条件为真以后,代码可以并发的切换到其他进程usleep
这个模拟夜漫长业务的过程这个漫长的业务过程中,可能有多个线程会进入该代码段--ticket
操作本身就不是一个原子操作
取出ticket–部分的汇编代码
objdump -d a.out > test.objdump152 40064b: 8b 05 e3 04 20 00 mov 0x2004e3(%rip),%eax
600b34 <ticket>153 400651: 83 e8 01 sub $0x1,%eax
154 400654: 89 05 da 04 20 00 mov %eax,0x2004da(%rip)
600b34 <ticket>
--
操作并不是原子操作而是对应三条汇编指令:
load
将共享变量体的从内存加载到寄存器update
更新寄存器里面的只执行复议操作- store:将新值从寄存器写回共享变量ticket的内存地址
要解决以上问题需要做到三点:
- 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他进程进入该临界区
- 如果多个线程同时要求进入临界区的代码,并且临界区没有线程在执行,那么只能一个线程进入该临界区
- 如果现场不在临界区中执行,那么该现场就不能阻止其他进程进入临界区
要做到这三点,本身是上就是需要一把锁,linux
上提供这把锁叫互斥量
互斥量的接口
初始化互斥量的两种方法
- 方法1:静态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
- ⽅法2,动态分配:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参数:
mutex:要初始化的互斥量
attr:这是一个指向 pthread_mutexattr_t 类型对象的指针,该类型用于定义互斥锁的属性。如果将其设置为 NULL
销毁互斥量
销毁互斥量需要注意:
- 使用
PTHREAD_ MUTEX_ INITIALIZER
初始化的互斥量不需要销毁 - 不要销毁⼀个已经加锁的互斥量
- 已经销毁的互斥量,要确保后⾯不会有线程再尝试加锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
互斥量加锁和解锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回o,失败返回错误号
调用pthread_ lock
时,可能会遇到以下情况:
- 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
- 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。
改进上面的售票系统:
#include <stdio.h>
#include <string>
#include <string.h>
#include <pthread.h>
#include <unistd.h>int ticket = 100;
pthread_mutex_t mutex;void* routine(void* args)
{char *id = (char*)args;// std::string id = static_cast<const char*>(args);while(1){pthread_mutex_lock(&mutex);if(ticket > 0){usleep(1000);printf("%s sells ticket:%d\n", id, ticket);ticket--;pthread_mutex_unlock(&mutex);}else{pthread_mutex_unlock(&mutex);break;}}return nullptr;
}int main(void)
{pthread_t t1, t2 , t3, t4;pthread_create(&t1, nullptr, routine, (void*)"thread 1");pthread_create(&t2, nullptr, routine, (void*)"thread 2");pthread_create(&t3, nullptr, routine, (void*)"thread 3");pthread_create(&t4, nullptr, routine, (void*)"thread 4");pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr);pthread_join(t4, nullptr);pthread_mutex_destroy(&mutex);return 0;
}
RAII风格的互斥锁,C++11也有,比如:
std : : mutex mtx;
std : : lock_guard guard ( mtx ) ;
🌠线程同步
🌉条件变量
- 当⼀个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
- 例如⼀个线程访问队列时,发现队列为空,它只能等待,只到其它线程将⼀个节点添加到队列中。这种情况就需要⽤到条件变量。
🌉同步概念与竞态条件
- 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从⽽有效避免饥饿问题,叫做同步
- 竞态条件:因为时序问题,⽽导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也
不难理解
🌉 条件变量函数
初始化
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t*restrict attr);
参数:
cond:要初始化的条件变量
attr: NULL
销毁:
int pthread_cond_destroy(pthread_cond_t *cond)
等待条件满⾜
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrictmutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量,后面详细解释
唤醒等待
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
简单案例:
- 我们先使用
PTHREAD_COND/MUTEX_INITIALIZER
进行测试,对其他细节暂不追究 - 然后将接口更改成为使用
pthread_cond_init/pthread_cond_destroy
的方式,方便后续进行封装
#include <iostream>
#include <string.h>
#include <unistd.h>
#include <pthread.h>pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void* active(void* args)
{std::string name = static_cast<const char*>(args);while(true){pthread_mutex_lock(&mutex);pthread_cond_wait(&cond, &mutex);std::cout<< name << "活动..." << std::endl;pthread_mutex_unlock(&mutex);}
}int main()
{pthread_t t1, t2;pthread_create(&t1, nullptr, active, (void*)"thread -1");pthread_create(&t2, nullptr, active, (void*)"thread -2");sleep(3);while(true){//对比测试pthread_cond_signal(&cond);//唤醒一个线程// pthread_cond_broadcast(&cond);//唤醒所有线程sleep(1);}pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_cond_destroy(&cond);pthread_mutex_destroy(&mutex);return 0;
}