多线程编程(c多线程编程)
多线程编程(c++多线程编程)
多线程编程可以说每个程序员的基本功,同时也是开发中的难点之一,本文以Linux C为例,讲述了线程的创建及常用的几种线程同步的方式,最后对多线程编程进行了总结与思考并给出代码示例。一、创建线程
多线程编程的第一步,创建线程。创建线程其实是增加了一个控制流程,使得同一进程中存在多个控制流程并发或者并行执行。
线程创建函数,其他函数这里不再列出,可以参考pthread.h。#include<pthread.h>intpthread_create(pthread_t*restrictthread,/*线程id*/ constpthread_attr_t*restrictattr,/*线程属性,默认可置为NULL,表示线程属性取缺省值*/ void*(*start_routine)(void*),/*线程入口函数*/ void*restrictarg/*线程入口函数的参数*/ );
代码示例:#include<stdio.h>#include<string.h>#include<stdlib.h>#include<unistd.h>#include<pthread.h>char*thread_func1(void*arg){pid_tpid=getpid();pthread_ttid=pthread_self();printf("%spid:%u,tid:%u(0x%x)\n",(char*)arg,(unsignedint)pid,(unsignedint)tid,(unsignedint)tid);char*msg="thread_func1";returnmsg; }void*thread_func2(void*arg){pid_tpid=getpid();pthread_ttid=pthread_self();printf("%spid:%u,tid:%u(0x%x)\n",(char*)arg,(unsignedint)pid,(unsignedint)tid,(unsignedint)tid);char*msg="thread_func2";while(1){printf("%srunning\n",msg); sleep(1); }returnNULL; }intmain(){pthread_ttid1,tid2;if(pthread_create(&tid1,NULL,(void*)thread_func1,"newthread:")!=0){printf("pthread_createerror.");exit(EXIT_FAILURE); }if(pthread_create(&tid2,NULL,(void*)thread_func2,"newthread:")!=0){printf("pthread_createerror.");exit(EXIT_FAILURE); } pthread_detach(tid2);char*rev=NULL; pthread_join(tid1,(void*)&rev);printf("%sreturn.\n",rev); pthread_cancel(tid2);printf("mainthreadend.\n");return0; }二、线程同步
有时候我们需要多个线程相互协作来执行,这时需要线程间同步。线程间同步的常用方法有:
互斥
信号量
条件变量
我们先看一个未进行线程同步的示例:#include<stdio.h>#include<string.h>#include<stdlib.h>#include<unistd.h>#include<pthread.h>#defineLEN100000intnum=0;void*thread_func(void*arg){for(inti=0;i<LEN;++i){ num+=1; } returnNULL; }intmain(){pthread_ttid1,tid2; pthread_create(&tid1,NULL,(void*)thread_func,NULL); pthread_create(&tid2,NULL,(void*)thread_func,NULL);char*rev=NULL; pthread_join(tid1,(void*)&rev); pthread_join(tid2,(void*)&rev);printf("correctresult=%d,wrongresult=%d.\n",2*LEN,num);return0; }
运行结果:correct result=200000, wrong result=106860.。
分享更多关于 Linux后端开发网络底层原理知识学习提升 点击正在跳转获取,完善技术栈,内容知识点包括Linux,Nginx,ZeroMQ,MySQL,Redis,线程池,MongoDB,ZK,Linux内核,CDN,P2P,epoll,Docker,TCP/IP,协程,DPDK等等。
【1】互斥
这个是最容易理解的,在访问临界资源时,通过互斥,限制同一时刻最多只能有一个线程可以获取临界资源。
其实互斥的逻辑就是:如果访问临界资源发现没有其他线程上锁,就上锁,获取临界资源,期间如果其他线程执行到互斥锁发现已锁住,则线程挂起等待解锁,当前线程访问完临界资源后,解锁并唤醒其他被该互斥锁挂起的线程,等待再次被调度执行。
"挂起等待"和"唤醒等待线程"的操作如何实现?每个Mutex有一个等待队列,一个线程要在Mutex上挂起等待,首先在把自己加入等待队列中,然后置线程状态为睡眠,然后调用调度器函数切换到别的线程。一个线程要唤醒等待队列中的其它线程,只需从等待队列中取出一项,把它的状态从睡眠改为就绪,加入就绪队列,那么下次调度器函数执行时就有可能切换到被唤醒的线程。
主要函数如下:#include<pthread.h>intpthread_mutex_init(pthread_mutex_t*restrictmutex, constpthread_mutexattr_t*restrictattr);/*初始化互斥量*/intpthread_mutex_destroy(pthread_mutex_t*mutex);/*销毁互斥量*/intpthread_mutex_lock(pthread_mutex_t*mutex);intpthread_mutex_trylock(pthread_mutex_t*mutex);intpthread_mutex_unlock(pthread_mutex_t*mutex);
用互斥解决上面计算结果错误的问题,示例如下:#include<stdio.h>#include<string.h>#include<stdlib.h>#include<unistd.h>#include<pthread.h>#defineLEN100000intnum=0;void*thread_func(void*arg){pthread_mutex_t*p_mutex=(pthread_mutex_t*)arg;for(inti=0;i<LEN;++i){ pthread_mutex_lock(p_mutex); num+=1; pthread_mutex_unlock(p_mutex); } returnNULL; }intmain(){pthread_mutex_tm_mutex; pthread_mutex_init(&m_mutex,NULL);pthread_ttid1,tid2; pthread_create(&tid1,NULL,(void*)thread_func,(void*)&m_mutex); pthread_create(&tid2,NULL,(void*)thread_func,(void*)&m_mutex); pthread_join(tid1,NULL); pthread_join(tid2,NULL); pthread_mutex_destroy(&m_mutex);printf("correctresult=%d,result=%d.\n",2*LEN,num);return0; }
运行结果:correct result=200000, result=200000.
如果在互斥中还嵌套有其他互斥代码,需要注意死锁问题。
产生死锁的两种情况:
一种情况是:如果同一个线程先后两次调用lock,在第二次调用时,由于锁已经被占用,该线程会挂起等待别的线程释放锁,然而锁正是被自己占用着的,该线程又被挂起而没有机会释放锁,因此就永远处于挂起等待状态了,产生死锁。
另一种典型的死锁情形是:线程A获得了锁1,线程B获得了锁2,这时线程A调用lock试图获得锁2,结果是需要挂起等待线程B释放锁2,而这时线程B也调用lock试图获得锁1,结果是需要挂起等待线程A释放锁1,于是线程A和B都永远处于挂起状态了。
如何避免死锁:
不用互斥锁(这个很多时候很难办到)
写程序时应该尽量避免同时获得多个锁。
如果一定有必要这么做,则有一个原则:如果所有线程在需要多个锁时都按相同的先后顺序(常见的是按Mutex变量的地址顺序)获得锁,则不会出现死锁。 (比如一个程序中用到锁1、锁2、锁3,它们所对应的Mutex变量的地址是锁1<锁2<锁3,那么所有线程在需要同时获得2个或3个锁时都应该按锁1、锁2、锁3的顺序获得。如果要为所有的锁确定一个先后顺序比较困难,则应该尽量使用pthread_mutex_trylock调用代替pthread_mutex_lock调用,以避免死锁。)【2】条件变量
条件变量概括起来就是:一个线程需要等某个条件成立(而这个条件是由其他线程决定的)才能继续往下执行,现在这个条件不成立,线程就阻塞等待,等到其他线程在执行过程中使这个条件成立了,就唤醒线程继续执行。
相关函数如下:#include<pthread.h>intpthread_cond_destroy(pthread_cond_t*cond);intpthread_cond_init(pthread_cond_t*restrictcond,constpthread_condattr_t*restrictattr);intpthread_cond_timedwait(pthread_cond_t*restrictcond,pthread_mutex_t*restrictmutex,conststructtimespec*restrictabstime);intpthread_cond_wait(pthread_cond_t*restrictcond,pthread_mutex_t*restrictmutex);intpthread_cond_broadcast(pthread_cond_t*cond);intpthread_cond_signal(pthread_cond_t*cond);
举个最容易理解条件变量的例子,"生产者-消费者"模式中,生产者线程向队列中发送数据,消费者线程从队列中取数据,当消费者线程的处理速度大于生产者线程时,会产生队列中没有数据了,一种处理办法是等待一段时间再次"轮询",但这种处理方式不太好,你不知道应该等多久,这时候条件变量可以很好的解决这个问题。下面是代码:#include<sys/types.h>#include<unistd.h>#include<stdlib.h>#include<stdio.h>#include<pthread.h>#include<errno.h>#include<string.h>#defineLIMIT1000structdata{ intn;structdata*next;};pthread_cond_tcondv=PTHREAD_COND_INITIALIZER;pthread_mutex_tmlock=PTHREAD_MUTEX_INITIALIZER; structdata*phead=NULL;voidproducer(void*arg){printf("producerthreadrunning.\n");intcount=0;for(;;){intn=rand()%100;structdata*nd=(structdata*)malloc(sizeof(structdata)); nd->n=n; pthread_mutex_lock(&mlock);structdata*tmp=phead; phead=nd; nd->next=tmp; pthread_mutex_unlock(&mlock); pthread_cond_signal(&condv); count+=n;if(count>LIMIT){break; } sleep(rand()%5); }printf("producercount=%d\n",count); }voidconsumer(void*arg){printf("consumerthreadrunning.\n");intcount=0;for(;;){ pthread_mutex_lock(&mlock);if(NULL==phead){ pthread_cond_wait(&condv,&mlock); }else{while(phead!=NULL){ count+=phead->n;structdata*tmp=phead; phead=phead->next;free(tmp); } } pthread_mutex_unlock(&mlock);if(count>LIMIT)break; }printf("consumercount=%d\n",count); }intmain(){pthread_ttid1,tid2; pthread_create(&tid1,NULL,(void*)producer,NULL); pthread_create(&tid2,NULL,(void*)consumer,NULL); pthread_join(tid1,NULL); pthread_join(tid2,NULL);return0; }
分享更多关于 Linux后端开发网络底层原理知识学习提升 点击正在跳转获取,完善技术栈,内容知识点包括Linux,Nginx,ZeroMQ,MySQL,Redis,线程池,MongoDB,ZK,Linux内核,CDN,P2P,epoll,Docker,TCP/IP,协程,DPDK等等。
完整视频链接点击:C/C++Linux服务器开发/后台架构师【零声学院】-学习视频教程-腾讯课堂
条件变量中的执行逻辑:
关键是理解执行到int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex) 这里时发生了什么,其他的都比较容易理解。执行这条函数前需要先获取互斥锁,判断条件是否满足,如果满足执行条件,则继续向下执行后释放锁;如果判断不满足执行条件,则释放锁,线程阻塞在这里,一直等到其他线程通知执行条件满足,唤醒线程,再次加锁,向下执行后释放锁。(简而言之就是:释放锁-->阻塞等待-->唤醒后加锁返回)
上面的例子可能有些繁琐,下面的这个代码示例则更为简洁:#include<sys/types.h>#include<unistd.h>#include<stdlib.h>#include<stdio.h>#include<pthread.h>#include<errno.h>#include<string.h>#defineNUM3pthread_cond_tcondv=PTHREAD_COND_INITIALIZER;pthread_mutex_tmlock=PTHREAD_MUTEX_INITIALIZER; voidproducer(void*arg){intn=NUM;while(n--){ sleep(1); pthread_cond_signal(&condv);printf("producerthreadsendnotifysignal.%d\t",NUM-n); } }voidconsumer(void*arg){intn=0;while(1){ pthread_cond_wait(&condv,&mlock);printf("recvproducerthreadnotifysignal.%d\n",++n);if(NUM==n){break; } } }intmain(){pthread_ttid1,tid2; pthread_create(&tid1,NULL,(void*)producer,NULL); pthread_create(&tid2,NULL,(void*)consumer,NULL); pthread_join(tid1,NULL); pthread_join(tid2,NULL);return0; }
运行结果:producerthreadsendnotifysignal.1recvproducerthreadnotifysignal.1producerthreadsendnotifysignal.2recvproducerthreadnotifysignal.2producerthreadsendnotifysignal.3recvproducerthreadnotifysignal.3【3】信号量
信号量适用于控制一个仅支持有限个用户的共享资源。用于保持在0至指定最大值之间的一个计数值。当线程完成一次对该semaphore对象的等待时,该计数值减一;当线程完成一次对semaphore对象的释放时,计数值加一。当计数值为0时,线程挂起等待,直到计数值超过0.
主要函数如下:#include<semaphore.h>intsem_init(sem_t*sem,intpshared,unsignedintvalue);intsem_wait(sem_t*sem);intsem_trywait(sem_t*sem);intsem_post(sem_t*sem);intsem_destroy(sem_t*sem);
代码示例如下:#include<sys/types.h>#include<unistd.h>#include<stdlib.h>#include<stdio.h>#include<pthread.h>#include<errno.h>#include<string.h>#include<semaphore.h>#defineNUM5intqueue[NUM];sem_tpsem,csem; voidproducer(void*arg){intpos=0;intnum,count=0;for(inti=0;i<12;++i){ num=rand()%100; count+=num; sem_wait(&psem);queue[pos]=num; sem_post(&csem);printf("producer:%d\n",num); pos=(pos+1)%NUM; sleep(rand()%2); }printf("producercount=%d\n",count); }voidconsumer(void*arg){intpos=0;intnum,count=0;for(inti=0;i<12;++i){ sem_wait(&csem); num=queue[pos]; sem_post(&psem);printf("consumer:%d\n",num); count+=num; pos=(pos+1)%NUM; sleep(rand()%3); }printf("consumercount=%d\n",count); } intmain(){ sem_init(&psem,0,NUM); sem_init(&csem,0,0);pthread_ttid[2]; pthread_create(&tid[0],NULL,(void*)producer,NULL); pthread_create(&tid[1],NULL,(void*)consumer,NULL); pthread_join(tid[0],NULL); pthread_join(tid[1],NULL); sem_destroy(&psem); sem_destroy(&csem);return0; }
信号量的执行逻辑:
当需要获取共享资源时,先检查信号量,如果值大于0,则值减1,访问共享资源,访问结束后,值加1,如果发现有被该信号量挂起的线程,则唤醒其中一个线程;如果检查到信号量为0,则挂起等待。三、多线程编程总结与思考
最后,我们对多线程编程进行总结与思考。
第一点就是在进行多线程编程时一定注意考虑同步的问题,因为多数情况下我们创建多线程的目的是让他们协同工作,如果不进行同步,可能会出现问题。
第二点,死锁的问题。在多个线程访问多个临界资源时,处理不当会发生死锁。如果遇到编译通过,运行时卡住了,有可能是发生死锁了,可以先思考一下是那些线程会访问多个临界资源,这样查找问题会快一些。
第三点,临界资源的处理,多线程出现问题,很大原因是多个线程访问临界资源时的问题,一种处理方式是将对临界资源的访问与处理全部放到一个线程中,用这个线程服务其他线程的请求,这样只有一个线程访问临界资源就会解决很多问题。
第四点,线程池,在处理大量短任务时,我们可以先创建好一个线程池,线程池中的线程不断从任务队列中取任务执行,这样就不用大量创建线程与销毁线程,这里不再细述。
车贷月供多少(贷款买车怎么算月供)我们买车的销售都会问是分期付款还是全款呢?下面我们来聊聊分期买车正确流程一先谈裸车价格把裸车价格砍到最低后,裸车价格少了购置税随之减少二车辆购置税计算方式购置税正确计算方式裸车价格
车贷的利息是多少(新车分期利息是多少)分期买车时,利息怎么算?费率和利率率怎么算,几厘几厘又是怎么算?这应该是很多人的一个问题,那这里面又有多少陷阱呢,今天我们就来说说利息那些事。一般买车的时候关于利息销售会给你说3种
贷款买车手续费(车贷10万手续费多少)贷款买车手续费(车贷10万手续费多少)在申请办理汽车借款时,你常常被规定缴纳一定的服务费,那么你清晰汽车借款有什么服务费?汽车贷款服务费怎么计算吗?汽车贷款有哪些手续费?1贷款购车
平安贷款利息多少(平安银行贷款20万36期)年中到了企业账单也越积越多不少老板都在为钱四处忙活今天,一起去看看神秘的老板朋友圈关于缺钱用钱的话题可不曾减少原来这就是老板生意圈里的秘密他们借款都喜欢找新微贷呀如果你也有相关需求
花呗分期怎么提前还款(花呗12期提前还款利息还算吗)当前,越来越多的人选择使用支付宝的花呗,来享受先消费后还款的购物体验。使用花呗购物,既没有利息,也没有手续费,还可以累积我们的芝麻信用分数。我们需要做的,只是在收到花呗账单后,在还
平安普惠贷款怎么样(平安普惠贷款20万36期)平安普惠到底是不是高利贷,如果被套路了该怎么办?前两天呢,看到一个女的在评论区里反映平安普惠贷款的问题。他说的问题呢,都是非常常见的,那我们呢就从因为我手里面有两份合同,给大家分析
贷款买房利息多少(50万房子分期付款利息怎么算)对于大多数年轻人来说,买房就是人生首等大事。然而我国楼市经历了几十年的繁荣期,房价也在不断上涨,从几百元每平的平均房价,至今已经上涨至了万元每平,虽然国民的收入水平也提高了,但是面
房贷利息为什么这么高(房贷三十年利息怎么算)欢迎收看本期内容,我是碎月,大家可以碎月导师,每天都有新内容分享,那这期内容我们就来看一下按揭买房这个话题。随着房价的不断上涨,很多朋友都只能望楼兴叹,想买房却没有钱,在这种一套房
贷款利息怎么算的(56万房子首付多少划算)如今房子已经是我们中国人的一个必需品了,但是房子动不动就是几十万几百万甚至上千万,那么我相信全中国百分之九十九的人都不能够一次性拿出来付全款,有的人也能拿出来但是因为各种原因不能够
谷歌怎么使用(google浏览器能直接用吗)谷歌浏览器app官方版也叫GoogleChrome,这是一款非常好用的手机浏览器软件,它不仅在桌面设备上表现卓越,在Android手机和平板电脑上也可让你畅享快速流畅的浏览体验。同
烟头的中心温度大概是多少度(烟头的表面温度是多少度)2月19日下午2时33分,奉贤区海湾旅游区海马路5792号附近绿化带发生火灾,造成部分绿化和树木烧损,无人员伤亡。经多方调查,此次绿化带着火系鲍某乱扔烟头,引燃地上枯草及树叶,导致
欧洲卡车模拟2怎么玩(欧洲卡车模拟2怎么退出)基础设定在游戏中,玩家需要作为一名卡车司机进行游戏。游戏初期可以进行角色性别头像名字的设定,并可通过启动器的档案编辑进行个人档案卡的变更。角色存在等级技能的设定,通过提升等级可获得
长江中游水位将复涨近日有关于长江中游水位将复涨的问题受到了很多网友们的关注,大多数网友都想要知道长江中游水位将复涨的具体情况,那么关于到长江中游水位将复涨的相关信息,小编也是在网上进行了一系列的信息
怎么装wifi(是不是买个路由器就有WiFi了)随着4G网络的普及,WLAN网络走进千家万户,同时随着智能手机越来越普及,人们对WiFi网络的需求也越来越大,但很多人却不会安装和设置路由器。本期文章就说说该如何安装路由器。一安装
两个路由器怎么串联(两个路由器怎么有线连接)最近这两年,多个无线路由器连接的方式最好的就是Mesh组网了。但Mesh组网是需要无线路由器软硬件的支持,而且各个品牌的Mesh技术不兼容,需要同一品牌的支持Mesh无线路由器才能
两个路由器如何连接(如何连接第二个路由器)很多朋友在架设路由器的时候,想要使两台路由器使用同一个WiFi网络但又不知道该如何操作。如果使用桥接串联的方式,除非关闭一台路由器的WiFi网络,不然两台路由器架设的WiFi网络是
有线路由器怎么用(有线路由器怎么安装设置)有线路由器怎么用(有线路由器怎么安装设置)有线路由器怎么安装设置是由花火网为您收集修改整理而来,更多相关内容请关注花火网互联网常识栏目。随着网络发展越来越快,家用网络设备也正在逐步
如何使用无线路由器(如何设置无线路由器的方法)如何使用无线路由器(如何设置无线路由器的方法)分享给朋友们一个简单的无线路由器入门设置教程,教大家怎么使用无线路由器如何设置使用,非常实用。一无线路由器基本使用方法1。拿到手的无线
斐讯路由器怎么样(斐讯的路由器为啥这么好)最近想买个电视盒子,在网上搜索相关信息,看到了一个曾经非常熟悉的品牌斐讯。现在不知道还有没有人记得斐讯,斐讯背后的一家公司叫做联壁金融,这两家公司当时是合作关系,利用免费的电视盒子
樱花的日文怎么写(日文我喜欢你怎么写)我们都知道,花落的时候很美丽,虽然有些淡淡悲凉。问一句,你知道表示落的日语吗?肯定有小伙伴回答说落!没错,这是一个,但该词语对应的花种你知道是什么吗?所以,在表达花落的时候,还是有
如何输入日文(手机上怎么手写日文)上次,少数派为日语初学者们介绍了计算机上日语输入法的一些基本知识,以及一些小技巧。但是在我们的电子生活中,仅仅知道计算机上的小技巧是不够的,我们也会经常使用手机等移动设备来与别人进
怎么打日文(手写输入日文)身边有几个日语比较好的朋友,他们表示国产手机只能输入汉字,有时想找首日文歌,还得先安装个日文输入法,非常麻烦。其实,如果使用华为手机,就不会有此烦恼啦因为它自带了日语输入哦,今天我