linux c编程一站式学习_linuxc编程一站式.pdf
多线程编程可以说每个程序员的基本功,同时也是开发中的难点之一,本文以Linux C为例,讲述了线程的创建及常用的几种线程同步的方式,最后对多线程编程进行了总结与思考并给出代码示例。一、创建线程多线程编程的第一步,创建线程。
多线程编程可以说每个程序员的基本功,同时也是开发中的难点之一,本文以Linux C为例,讲述了线程的创建及常用的几种线程同步的方式,最后对多线程编程进行了总结与思考并给出代码示例一、创建线程多线程编程的第一步,创建线程。
创建线程其实是增加了一个控制流程,使得同一进程中存在多个控制流程并发或者并行执行线程创建函数,其他函数这里不再列出,可以参考pthread.h#includeintpthread_create。
( pthread_t *restrict thread, /*线程id*/constpthread_attr_t *restrict attr, /*线程属性,默认可置为NULL,表示线程属性取缺省值*/
void *(*start_routine)(void*), /*线程入口函数*/void *restrict arg /*线程入口函数的参数*/ );代码示例:#include
#include#include#include#includechar* thread_func1(void* arg)
{ pid_t pid = getpid(); pthread_t tid = pthread_self(); printf("%s pid: %u, tid: %u (0x%x)\n"
, (char*)arg, (unsignedint)pid, (unsignedint)tid, (unsignedint)tid); char* msg = "thread_func1";
return msg; } void* thread_func2(void* arg){ pid_t pid = getpid(); pthread_t tid = pthread_self();
printf("%s pid: %u, tid: %u (0x%x)\n", (char*)arg, (unsignedint)pid, (unsignedint)tid, (unsignedint)tid);
char* msg = "thread_func2 "; while(1) { printf("%s running\n", msg); sleep(1); }
returnNULL; } intmain(){ pthread_t tid1, tid2; if (pthread_create(&tid1, NULL, (void*)thread_func1,
"new thread:") != 0) { printf("pthread_create error."); exit(EXIT_FAILURE); }
if (pthread_create(&tid2, NULL, (void*)thread_func2, "new thread:") != 0) { printf("pthread_create error."
); exit(EXIT_FAILURE); } pthread_detach(tid2); char* rev = NULL; pthread_join(tid1, (
void *)&rev); printf("%s return.\n", rev); pthread_cancel(tid2); printf("main thread end.\n"
); return0; }二、线程同步有时候我们需要多个线程相互协作来执行,这时需要线程间同步线程间同步的常用方法有:互斥信号量条件变量我们先看一个未进行线程同步的示例:#include。
#include#include#include#include#define LEN 100000int num =
0; void* thread_func(void* arg){ for (int i = 0; i< LEN; ++i) { num += 1; }
returnNULL; } intmain(){ pthread_t tid1, 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("correct result=%d, wrong result=%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上挂起等待,首先在把自己加入等待队列中,然后置线程状态为睡眠,然后调用调度器函数切换到别的线程一个线程要唤醒等待队列中的其它线程,只需从等待队列中取出一项,把它的状态从睡眠改为就绪,加入就绪队列,那么下次调度器函数执行时就有可能切换到被唤醒的线程。
主要函数如下:#includeintpthread_mutex_init(pthread_mutex_t *restrict mutex, constpthread_mutexattr_t
*restrict attr); /*初始化互斥量*/intpthread_mutex_destroy(pthread_mutex_t *mutex); /*销毁互斥量*/int
pthread_mutex_lock(pthread_mutex_t *mutex); intpthread_mutex_trylock(pthread_mutex_t *mutex); intpthread_mutex_unlock
(pthread_mutex_t *mutex);用互斥解决上面计算结果错误的问题,示例如下:#include#include#include#
include#include#define LEN 100000int num = 0; void* thread_func(void* arg){
pthread_mutex_t* p_mutex = (pthread_mutex_t*)arg; for (int i = 0; i< LEN; ++i) { pthread_mutex_lock(p_mutex); num +=
1; pthread_mutex_unlock(p_mutex); } returnNULL; } intmain(){ pthread_mutex_t
m_mutex; pthread_mutex_init(&m_mutex, NULL); pthread_t tid1, 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("correct result=%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】条件变量条件变量概括起来就是:一个线程需要等某个条件成立(而这个条件是由其他线程决定的)才能继续往下执行,现在这个条件不成立,线程就阻塞等待,等到其他线程在执行过程中使这个条件成立了,就唤醒线程继续执行。
相关函数如下:#includeintpthread_cond_destroy(pthread_cond_t *cond); intpthread_cond_init(pthread_cond_t
*restrict cond, constpthread_condattr_t *restrict attr); intpthread_cond_timedwait(pthread_cond_t
*restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime)
; intpthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); intpthread_cond_broadcast
(pthread_cond_t *cond); intpthread_cond_signal(pthread_cond_t *cond);举个最容易理解条件变量的例子,“生产者-消费者”模式中,生产者线程向队列中发送数据,消费者线程从队列中取数据,当消费者线程的处理速度大于生产者线程时,会产生队列中没有数据了,一种处理办法是等待一段时间再次“轮询”,但这种处理方式不太好,你不知道应该等多久,这时候条件变量可以很好的解决这个问题。
下面是代码:#include#include#include#include#include#include
#include#define LIMIT 1000structdata {int n; structdata* next; }; pthread_cond_t
condv = PTHREAD_COND_INITIALIZER; pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER; structdata* phead
= NULL;voidproducer(void* arg){ printf("producer thread running.\n"); int count = 0; for
(;;) { int n = 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("producer count=%d\n"
, count); } voidconsumer(void* arg){ printf("consumer thread running.\n"); int count = 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("consumer count=%d\n", count); } intmain(){
pthread_t tid1, tid2; pthread_create(&tid1, NULL, (void*)producer, NULL); pthread_create(&tid2,
NULL, (void*)consumer, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); return
0; }分享更多关于 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#include#include
#include#include#include#include#define NUM 3pthread_cond_t
condv = PTHREAD_COND_INITIALIZER; pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER; voidproducer(
void* arg){ int n = NUM; while(n--) { sleep(1); pthread_cond_signal(&condv);
printf("producer thread send notify signal. %d\t", NUM-n); } } voidconsumer(void* arg){ int n =
0; while (1) { pthread_cond_wait(&condv, &mlock); printf("recv producer thread notify signal. %d\n"
, ++n); if (NUM == n) { break; } } } intmain(){ pthread_t tid1, tid2; pthread_create(&tid1,
NULL, (void*)producer, NULL); pthread_create(&tid2, NULL, (void*)consumer, NULL); pthread_join(tid1,
NULL); pthread_join(tid2, NULL); return0; }运行结果:producer thread send notify signal. 1recv producer thread notify signal.
1 producer thread send notify signal. 2recv producer thread notify signal. 2 producer thread send notify signal.
3recv producer thread notify signal. 3【3】信号量信号量适用于控制一个仅支持有限个用户的共享资源用于保持在0至指定最大值之间的一个计数值当线程完成一次对该semaphore对象的等待时,该计数值减一;当线程完成一次对semaphore对象的释放时,计数值加一。
当计数值为0时,线程挂起等待,直到计数值超过0.主要函数如下:#includeintsem_init(sem_t *sem, int pshared, unsignedint value)
; intsem_wait(sem_t *sem); intsem_trywait(sem_t *sem); intsem_post(sem_t * sem); intsem_destroy(sem_t
* sem);代码示例如下:#include#include#include#include#include
#include#include#include#define NUM 5intqueue[NUM]; sem_t psem, csem;
voidproducer(void* arg){ int pos = 0; int num, count = 0; for (int i=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
("producer count=%d\n", count); } voidconsumer(void* arg){ int pos = 0; int num, count = 0;
for (int i=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("consumer count=%d\n", count); } intmain(){ sem_init(&psem, 0, NUM); sem_init(&csem,
0, 0); pthread_t tid[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,则挂起等待。
三、多线程编程总结与思考最后,我们对多线程编程进行总结与思考第一点就是在进行多线程编程时一定注意考虑同步的问题,因为多数情况下我们创建多线程的目的是让他们协同工作,如果不进行同步,可能会出现问题第二点,死锁的问题。
在多个线程访问多个临界资源时,处理不当会发生死锁如果遇到编译通过,运行时卡住了,有可能是发生死锁了,可以先思考一下是那些线程会访问多个临界资源,这样查找问题会快一些第三点,临界资源的处理,多线程出现问题,很大原因是多个线程访问临界资源时的问题,一种处理方式是将对临界资源的访问与处理全部放到一个线程中,用这个线程服务其他线程的请求,这样只有一个线程访问临界资源就会解决很多问题。
第四点,线程池,在处理大量短任务时,我们可以先创建好一个线程池,线程池中的线程不断从任务队列中取任务执行,这样就不用大量创建线程与销毁线程,这里不再细述。
- 标签:
- 编辑:
- 相关文章
-
linux c编程一站式学习_linuxc编程一站式.pdf
多线程编程可以说每个程序员的基本功,同时也是开发中的难点之一,本文以Linux C为例,讲述了线程的创建及常用的几种线程同步的方式,最…
-
销售应该学习的销售技巧_销售的学问和技巧
直接上干货1、你的个人形象的得体程度,虽然不一定能给顾客留下好的第一印象,但是所有好的第一印象,90%都产生于你的服装和妆容。2、…
- 求一年级数学学习方法_一年级数学的几种方法
- 学习上海话_学上海话视频教程1-10课
- 学习太极拳怎样才算入门_初学太极拳如何入门
- 适合初中生几种特别的学习方法_适合初中生的衣服品牌
- 短期烹饪学习班_短期烹饪培训学的什么