网站首页 > 技术文章 正文
一、多线程计数
背景:
火车抢票,总共10个窗口,每个窗口都同时进行10w张抢票
可以采用多线程的方式,火车票计数是公共的任务
#include<pthread.h>//posix线程
#include<stdio.h>
#include<unistd.h>
#define THREAD_COUNT 10 //定义线程数10
//线程入口函数
void* thread_callback(void* arg){
int* pcount=(int*)arg;
int i=0;
while(i++<100000){
(*pcount)++;
usleep(1);//单位微秒
}
}
//10个窗口,同时对count进行++操作
int main(){
pthread_t threadid[THREAD_COUNT]={0};//初始化线程id
int count=0;
for(int i=0;i<THREAD_COUNT;i++){//创建10个线程
//第一个参数:返回线程。 第二个参数:线程的属性(堆栈)。第三个:线程的入口函数。第四个:主线程往子线程传的参数
pthread_create(&threadid[i],NULL,thread_callback,&count);//count是传入thread_callback内的
}
for(int i=0;i<100;i++){
printf("count: %d\n",count);
sleep(1);//单位秒
}
}
虽然包含了线程的头文件<pthread.h>,可是编译的时候却报错“对pthread_create未定义的引用“,原来时因为 pthread库不是Linux系统默认的库,连接时需要使用库libpthread.a,所以在使用pthread_create创建线程时,在编译中要加-lpthread参数
可以使用下面方法编译
g++ thread_count.cpp -o thread_count -lpthread
执行后,发现,按理说要执行到100w,可是停到99w多就结束了。
二、发现问题
理想状态,线程应该是这样的
?但实际上存在,执行完线程1MOV操作后,线程1切换到线程2。导致两个线程的操作,本应该50->52,但是结果确实50->51
?count是一个临界资源(两个线程共享一个变量),因此为了避免上述这种情况发生,要加锁
相关视频推荐
自旋锁、互斥锁、信号量、原子操作、条件变量在不同开源框架的应用
学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂
需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享
三、互斥锁
当一个线程在执行一个指令的时候,另一个线程进不来。
相当于把count++转化为汇编的3行命令给打包在一起。
定义互斥锁
pthread_mutex_t mutex;//定义互斥锁
初始化互斥锁
pthread_mutex_init(&mutex,NULL);//互斥锁初始化(第二个参数是 锁的属性)
加锁/解锁
//加了互斥锁
pthread_mutex_lock(&mutex);
(*pcount)++;
pthread_mutex_unlock(&mutex);
完整代码
#include<pthread.h>
#include<stdio.h>
#include<unistd.h>
#define THREAD_COUNT 10
pthread_mutex_t mutex;//定义互斥锁
void* thread_callback(void* arg){
int* pcount=(int*)arg;
int i=0;
while(i++<100000){
#if 0
(*pcount)++;
#else
//加了互斥锁
pthread_mutex_lock(&mutex);
(*pcount)++;
pthread_mutex_unlock(&mutex);
#endif
usleep(1);//单位微秒
}
}
//10个窗口,同时对count进行++操作
int main(){
pthread_t threadid[THREAD_COUNT]={0};//初始化线程id
pthread_mutex_init(&mutex,NULL);//互斥锁初始化(第二个参数是 锁的属性)
int count=0;
for(int i=0;i<THREAD_COUNT;i++){//创建10个线程
//第一个参数:返回线程。 第二个参数:线程的属性(堆栈)。第三个:线程的入口函数。第四个:主线程往子线程传的参数
pthread_create(&threadid[i],NULL,thread_callback,&count);//count是传入thread_callback内的
}
for(int i=0;i<100;i++){
printf("count: %d\n",count);
sleep(1);//单位秒
}
}
四、自旋锁
在写法上和互斥锁基本上没有差别
定义自旋锁
pthread_spinlock_t spinlock;//定义自旋锁
初始化自旋锁
pthread_spin_init(&spinlock,PTHREAD_PROCESS_SHARED);//自旋锁初始化(第二个参数是 进程共享)
自旋锁(加锁/解锁)
//加了自旋锁
pthread_spin_lock(&spinlock);
(*pcount)++;
pthread_spin_unlock(&spinlock);
完整代码
#include<pthread.h>
#include<stdio.h>
#include<unistd.h>
#define THREAD_COUNT 10
// pthread_mutex_t mutex;
pthread_spinlock_t spinlock;//定义自旋锁
void* thread_callback(void* arg){
int* pcount=(int*)arg;
int i=0;
while(i++<100000){
#if 0
(*pcount)++;
#elif 0
//加了互斥锁
pthread_mutex_lock(&mutex);
(*pcount)++;
pthread_mutex_unlock(&mutex);
#else
//加了自旋锁
pthread_spin_lock(&spinlock);
(*pcount)++;
pthread_spin_unlock(&spinlock);
#endif
usleep(1);
}
}
int main(){
pthread_t threadid[THREAD_COUNT]={0};
// pthread_mutex_init(&mutex,NULL);
pthread_spin_init(&spinlock,PTHREAD_PROCESS_SHARED);//自旋锁初始化(第二个参数是 进程共享)
int count=0;
for(int i=0;i<THREAD_COUNT;i++){
pthread_create(&threadid[i],NULL,thread_callback,&count);
}
for(int i=0;i<100;i++){
printf("count: %d\n",count);
sleep(1);//单位秒
}
}
五、互斥锁和自旋锁的对比
使用场景:
当锁的内容很少的时候,继续等待的时间代价比 线程切换的时间代价更小的 时候,选择使用自旋锁(因为互斥锁会切换线程,等待重新调度请求,判断锁是否被占用,如果占用,继续阻塞,并切换到其他线程。如果切换线程的代价比 等待的代价大,可以使用自旋锁。否则使用互斥锁)。
锁的内容比较多的时候,使用互斥锁。(比如,线程安全的红黑树,可以使用mutex)
也就是说:
锁的内容少/没有系统调用,等待的时间代价少-》用自旋锁
锁的内容多,等待时间代价大-》用互斥锁
六、原子操作
如果把这三条指令变成一条,那么就不会出现,这种问题了。
?原子操作:单条cpu指令实现(因此使用范围有限,必须要是cpu指令中有的指令)
?完整代码
#include<pthread.h>
#include<stdio.h>
#include<unistd.h>
#define THREAD_COUNT 10
// pthread_mutex_t mutex;
// pthread_spinlock_t spinlock;
//用一个函数去实现
int increase(int *value,int add){
int old;
__asm__ volatile(
"lock;xaddl %2,%1;"
:"=a"(old)
:"m"(*value),"a"(add)
:"cc","memory"
);
return old;
}
void* thread_callback(void* arg){
int* pcount=(int*)arg;
int i=0;
while(i++<100000000){
#if 0
(*pcount)++;
#elif 0
//加了互斥锁
pthread_mutex_lock(&mutex);
(*pcount)++;
pthread_mutex_unlock(&mutex);
#elif 0
//加了自旋锁
pthread_spin_lock(&spinlock);
(*pcount)++;
pthread_spin_unlock(&spinlock);
#else
//原子操作
increase(pcount,1);
#endif
usleep(1);
}
}
int main(){
pthread_t threadid[THREAD_COUNT]={0};
// pthread_mutex_init(&mutex,NULL);
// pthread_spin_init(&spinlock,PTHREAD_PROCESS_SHARED);//自旋锁初始化(第二个参数是 进程共享)
int count=0;
for(int i=0;i<THREAD_COUNT;i++){
pthread_create(&threadid[i],NULL,thread_callback,&count);
}
for(int i=0;i<100;i++){
printf("count: %d\n",count);
sleep(1);//单位秒
}
}
七、其他:CAS
CAS:compare and swap
cpu有这样一条指令cmpxchg(a,b,c),(其实就是原子操作的原理)。
它的意思是
if(a==b){
a=c;
}
下面例子中,instance就是a,NULL是b,c是malloc(sizeof(object))
if(instance==NULL){
instance=malloc(sizeof(object));
}
使用cas实现求和
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
#define CAS(a_ptr, a_oldVal, a_newVal) __sync_bool_compare_and_swap(a_ptr, a_oldVal, a_newVal)
// #define AtomicAdd(a_ptr,a_count) __sync_fetch_and_add (a_ptr, a_count)
#define THREAD_COUNT 10
void* callback(void* arg){
int* pcount=(int*)arg;
for(int i=0;i<100000;i++){
while(true){
int current=(*pcount);
if(CAS(pcount,current,current+1)) break;
}
usleep(1);
}
return (void*)nullptr;
}
int main(int argc,char** argv){
pthread_t threadid[THREAD_COUNT]={0};
int count=0;
for(int i=0;i<THREAD_COUNT;i++){//创建10个线程
pthread_create(&threadid[i],NULL,callback,&count);
}
for(int i=0;i<100;i++){
printf("count: %d\n",count);
sleep(1);//单位秒
}
}
猜你喜欢
- 2024-10-12 漫画 | Linux 并发和竞态问题究竟是什么?
- 2024-10-12 【驱动】串口驱动分析(三)-serial driver
- 2024-10-12 synchronized锁 synchronized锁的是类还是对象
- 2024-10-12 Golang 程序遇到性能问题该怎么办?
- 2024-10-12 线程间通信——互斥锁 线程间互斥方式
- 2024-10-12 【Linux系统编程】互斥锁 linux 互斥锁优先级反转
- 2024-10-12 每行代码都带注释,带你看懂Go互斥锁的源码
- 2024-10-12 一文搞懂pprof 一文搞懂伤寒论六经辨证
- 2024-10-12 并发原理系列八:信号量、互斥锁、自旋锁
- 2024-10-12 GoLang 初识Channel管道 golang 命名管道
你 发表评论:
欢迎- 最近发表
-
- 在 Spring Boot 项目中使用 activiti
- 开箱即用-activiti流程引擎(active 流程引擎)
- 在springBoot项目中整合使用activiti
- activiti中的网关是干什么的?(activiti包含网关)
- SpringBoot集成工作流Activiti(完整源码和配套文档)
- Activiti工作流介绍及使用(activiti工作流会签)
- SpringBoot集成工作流Activiti(实际项目演示)
- activiti工作流引擎(activiti工作流引擎怎么用)
- 工作流Activiti初体验及在数据库中生成的表
- Activiti工作流浅析(activiti6.0工作流引擎深度解析)
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)