每个程序员应该彻底掌握的多线程编程(Linux C)
多线程编程可以说每个程序员的基本功,同时也是开发中的难点之一,本文以LinuxC为例,讲述了线程的创建及常用的几种线程同步的方式,最后对多线程编程进行了总结与思考并给出代码示例。
一、创建线程多线程编程的第一步,创建线程。创建线程其实是增加了一个控制流程,使得同一进程中存在多个控制流程并发或者并行执行。
线程创建函数,其他函数这里不再列出,可以参考。
*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("mainthread.\n");return0;}二、线程同步有时候我们需要多个线程相互协作来执行,这时需要线程间同步。线程间同步的常用方法有:
互斥
信号量
条件变量
我们先看一个未进行线程同步的示例:
=0;void*thread_func(void*arg){for(inti=0;iLEN;++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;}运行结果:correctresult=200000,wrongresult=106860.。

这个是最容易理解的,在访问临界资源时,通过互斥,限制同一时刻最多只能有一个线程可以获取临界资源。
其实互斥的逻辑就是:如果访问临界资源发现没有其他线程上锁,就上锁,获取临界资源,期间如果其他线程执行到互斥锁发现已锁住,则线程挂起等待解锁,当前线程访问完临界资源后,解锁并唤醒其他被该互斥锁挂起的线程,等待再次被调度执行。
主要函数如下:
_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);
举个最容易理解条件变量的例子,“生产者-消费者”模式中,生产者线程向队列中发送数据,消费者线程从队列中取数据,当消费者线程的处理速度大于生产者线程时,会产生队列中没有数据了,一种处理办法是等待一段时间再次“轮询”,但这种处理方式不太好,你不知道应该等多久,这时候条件变量可以很好的解决这个问题。下面是代码:
{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(countLIMIT){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(countLIMIT)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;}
条件变量中的执行逻辑:
关键是理解执行到intpthread_cond_wait(pthread_cond_t*restrictcond,pthread_mutex_t*restrictmutex)这里时发生了什么,其他的都比较容易理解。执行这条函数前需要先获取互斥锁,判断条件是否满足,如果满足执行条件,则继续向下执行后释放锁;如果判断不满足执行条件,则释放锁,线程阻塞在这里,一直等到其他线程通知执行条件满足,唤醒线程,再次加锁,向下执行后释放锁。(简而言之就是:释放锁--阻塞等待--唤醒后加锁返回)
上面的例子可能有些繁琐,下面的这个代码示例则更为简洁:
_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("producerthreadsnotifysignal.%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;}运行结果:
【3】信号量信号量适用于控制一个仅支持有限个用户的共享资源。用于保持在0至指定最大值之间的一个计数值。当线程完成一次对该semaphore对象的等待时,该计数值减一;当线程完成一次对semaphore对象的释放时,计数值加一。当计数值为0时,线程挂起等待,直到计数值超过0.
主要函数如下:
includesys/[NUM];sem_tpsem,csem;voidproducer(void*arg){intpos=0;intnum,count=0;for(inti=0;i12;++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;i12;++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,则挂起等待。
三、多线程编程总结与思考最后,我们对多线程编程进行总结与思考。
第一点就是在进行多线程编程时一定注意考虑同步的问题,因为多数情况下我们创建多线程的目的是让他们协同工作,如果不进行同步,可能会出现问题。
第二点,死锁的问题。在多个线程访问多个临界资源时,处理不当会发生死锁。如果遇到编译通过,运行时卡住了,有可能是发生死锁了,可以先思考一下是那些线程会访问多个临界资源,这样查找问题会快一些。
第三点,临界资源的处理,多线程出现问题,很大原因是多个线程访问临界资源时的问题,一种处理方式是将对临界资源的访问与处理全部放到一个线程中,用这个线程服务其他线程的请求,这样只有一个线程访问临界资源就会解决很多问题。
第四点,线程池,在处理大量短任务时,我们可以先创建好一个线程池,线程池中的线程不断从任务队列中取任务执行,这样就不用大量创建线程与销毁线程,这里不再细述。







