30天速通Linux 第八章多线程编程
学习资料https://pan.baidu.com/s/1NZpC4NyH0OHGOPPbO9J10g?pwdh9m2 提取码: h9m2一. 多线程概念进程是资源分配单位线程是 CPU 调度单位又叫轻量级进程。同一进程的线程共享地址空间、文件描述符、信号处理等切换代价远小于进程。Linux 线程是用户级线程遵循 POSIX 接口pthreadpthread.h编译加-pthread。为什么用多线程开销小响应快 / 共享数据通信方便 / 耗时操作放新线程不影响主线程响应 / 多 CPU 并行。⚠ 共享内存带来便利也带来风险访问共享资源必须做同步与互斥。二. 线程编程2.1 创建pthread_createintpthread_create(pthread_t*tid,constpthread_attr_t*attr,void*(*start_rtn)(void*),void*arg);// 成功 0失败返回错误码别用 errnoarg传参传结构体指针即可线程内强转回来。进程的所有信息代码、全局/堆内存、栈、fd对所有线程共享。typedefstructstudent{intage;charname[20];}STU;void*create(void*arg){STU*temp(STU*)arg;printf(STU age is %d, name is %s\n,temp-age,temp-name);}STU*stumalloc(sizeof(STU));stu-age20;strcpy(stu-name,abcdefg);pthread_create(tidp,NULL,create,(void*)stu);pthread_join(tidp,NULL);2.2 终止pthread_exitvoidpthread_exit(void*ret_val);// ret_val 交给 pthread_join正常终止pthread_exit或线程函数return。⚠ 线程里调exit()会结束整个进程pthread_exitexit影响只结束当前线程整个进程结束清理函数先进后出只对pthread_exit有效voidpthread_cleanup_push(void(*rtn)(void*),void*arg);voidpthread_cleanup_pop(intexecute);// 0 不执行非 0 执行pthread_cleanup_push(clean_1,thread first handler);pthread_cleanup_push(clean_1,thread second hadler);if(i1)pthread_exit(NULL);// 执行清理顺序是 second → first后进先出elseexit(0);// 对 exit 无效pthread_cleanup_pop(0);pthread_cleanup_pop(0);两个调用必须配套线程正常return或exit时清理函数不会执行。2.3 等待pthread_joinintpthread_join(pthread_tthread,void**thread_return);// 挂起自己等指定线程结束只能等待同一进程的线程⚠ 一个线程不能被多个线程 join。void*t2_exe(void*arg){pthread_join((pthread_t)arg,NULL);// 先把线程1 join 掉printf(The second thread:\n);// 线程1结束后才打印 6~10}pthread_create(pid1,NULL,t1_exe,NULL);pthread_create(pid2,NULL,t2_exe,(void*)pid1);2.4 获取自身线程号pthread_tpthread_self(void);// 同一进程内 pid 相同线程 ID 不同三. 同步与互斥三种机制互斥量、信号量、条件变量。3.1 互斥量 mutex相当于一把锁保证①原子性临界区要么全做要么不做②唯一性解锁前别人锁不上③非繁忙等待抢不到锁就挂起不占 CPU。操作函数初始化pthread_mutex_init(mutex, NULL)或静态PTHREAD_MUTEX_INITIALIZER加锁pthread_mutex_lock(mutex)抢不到就阻塞尝试加锁pthread_mutex_trylock(mutex)占用时返回EBUSY不阻塞解锁pthread_mutex_unlock(mutex)一般由加锁者解锁销毁pthread_mutex_destroy(mutex)锁的 4 种属性PTHREAD_MUTEX_TIMED_NP默认普通锁排队公平RECURSIVE_NP嵌套锁可重复加ERRORCHECK_NP检错锁重复加锁返回EDEADLKADAPTIVE_NP适应锁。示例两个线程按顺序遍历数组charstr[]abcdefghijklmnopqrstuvwxyz123456789;pthread_mutex_tmutex;intindex20;void*t1_exe(void*arg){while(index2strlen(str)-1){pthread_mutex_lock(mutex);// 不加锁会出现元素重复打印printf(The %dth element is %c\n,index2,str[index2]);sleep(1);index2;pthread_mutex_unlock(mutex);}}// main: pthread_mutex_init(mutex, NULL); 创建两个线程都跑 t1_exe3.2 信号量sem_t头文件semaphore.h互斥用 1 个同步用多个。本质是 P/V 原语 非负计数器。函数作用int sem_init(sem_t *sem, int pshared, unsigned int value);pshared0线程间共享放全局/堆非 0 进程间共享放共享内存int sem_wait(sem_t *sem);P值为 0 阻塞否则减 1int sem_trywait(sem_t *sem);P 非阻塞版int sem_post(sem_t *sem);V加 1 并唤醒一个等待者原子int sem_destroy(sem_t *sem);销毁sem_getvalue取当前值互斥一个信号量初值 1顺序随机但不同时访问sem_init(sem,0,1);// 线程1 // 线程2sem_wait(sem);sem_wait(sem);global;global*2;sem_post(sem);sem_post(sem);同步两个信号量轮流执行sem_init(sem1,0,1);sem_init(sem2,0,0);// 线程1: sem_wait(sem1); global; sem_post(sem2);// 线程2: sem_wait(sem2); global*2; sem_post(sem1);// 结果固定1→2→4→5→10…3.3 条件变量与互斥锁不同它用来等待而不是上锁通常与互斥锁配合。一个线程等条件成立而挂起另一个线程使条件成立并通知。好处避免线程反复加锁/解锁却不干活的忙等。函数作用pthread_cond_init(cond, NULL)/PTHREAD_COND_INITIALIZER初始化pthread_cond_wait(cond, mutex)解锁 mutex → 阻塞等待 → 被唤醒后重新加锁pthread_cond_timedwait(cond, mutex, abstime)超时返回ETIMEpthread_cond_signal(cond)唤醒一个等待线程pthread_cond_destroy(cond)释放⚠pthread_cond_wait返回后必须重新判断条件解锁→等待→加锁这三步不是原子操作等待期间条件可能已被别的线程改变。pthread_mutex_tmutex;pthread_cond_tcond;inti1;void*thread1(void*arg)// i 为 9 的倍数时通知 thread2{for(i1;iMAX;i){pthread_mutex_lock(mutex);if(i%90)pthread_cond_signal(cond);elseprintf(In thread1:%d\n,i);pthread_mutex_unlock(mutex);sleep(1);}}void*thread2(void*flag)// 不是 9 的倍数就等{while(iMAX){pthread_mutex_lock(mutex);if(i%9!0)pthread_cond_wait(cond,mutex);if(i%90)printf(In thread2:%d\n,i);pthread_mutex_unlock(mutex);sleep(1);}}四. 三种机制怎么选机制着眼点互斥量共享资源的互斥访问临界区同时只进一个线程信号量互斥1 个 同步多个条件变量等待某个条件成立配合互斥量避免忙等互斥/同步会降低一点效率但能保证共享资源一致按需选择即可。