第四讲 多线程Pthread编程
共享内存系统和分布式内存模型回顾
伪共享
cache按照行读取
当多个处理器访问同一行,即使访问的是不同的机器字,也会潜在竞争
会产生不必要的协同开销
- 当数据很少的时候,Core1和Core0访问的是同一行,同一个缓存行里的不同变量在同时被修改
共享内存编程
- 动态线程
- 主线程等待计算工作,fork新线程分配工作,工作线程完成任务后结束
- 资源利用率高
- 主线程完成时fork出所有线程
- 性能更优,但可能浪费系统资源
并行程序设计的复杂性
POSIX Threads编程
基本概念
线程库:
- Pthread是POSIX标准
- 相对底层
- 可移植
- OpenMP是新标准
- 高层编程,适用于共享内存架构上的科学计算
POSIX Thread
基础API
创建线程
int pthread_create(pthread_t*,const pthread_attr_t*,void*(*)(void*),void*)
//pthread_t不透明,程序员不可操作
//调用
errcode=pthread_create(&thread_id,&thread_attribute,&thread_fun,&fun_arg);
- thread_id
- 指针:线程ID或句柄(用于停止线程)
- thread_attribute:
- 各种属性,通常用空指针NULL表示标准默认属性值
- thread_fun
- 新线程要运行的函数(参数和返回值类型都是void*)
- fun_arg
- 传递给要运行的函数thread_fun的参数
- errorocode
- 若创建失败,返回非零值
![image-20221031151330065](/Users/zhangxiaoni/Library/Application Support/typora-user-images/image-20221031151330065.png)
Pthread “hello world”程序
![image-20221031152746925](/Users/zhangxiaoni/Library/Application Support/typora-user-images/image-20221031152746925.png)
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
int thread_count;
void* Hello(void* rank)
int main(int argc,char* argv[]){
long thread;
pthread_t* thread_handles;
thread_count=strto(argv[1],NULL,10);
thread_handles=(pthread_t*)malloc(thread_count*sizeof(pthread_t*));
for(thread=0;thread<thread_count;thread++)
pthread_create(&thread_handles[thread],NULL,Hello,(void*)thread);
printf("Hello from the main thread\n");
for(thread=0;thread<thread_count;thread++);
pthread_join(thread_handles[thread],NULL);
free(thread_handles);
return 0;
}
Pthread其他基础API
取消、结束线程
void pthread_exit(void *value_ptr)
- 显式取消线程
- 通过value_ptr返回结果给调用者
int pthread_cnacel(pthread_t thread)
取消线程thread执行
同步
例子 估算pai
多线程版本
问题:每个线程都要把数加到sum上面,会存在竞争,结果是错误的
概念回顾
原子性
- 一组操作要么全部执行要么全不执行,则称其是原子性的
临界区
- 共享资源的代码段,一次只能允许一个线程执行该代码
竞争条件
- 多个线程/进程尝试更新同一个共享资源时,结果可能是无法预测的,则存在竞争
- 如果存在竞争则创建临界区
数据依赖
- 两个内存的序。为了保证结果正确性,必须保持这个序
同步
- 时间上强制使各执行进程/线程在某一点必须互相等待,确保各进程/线程的正常顺序和对共享可写数据的正确访问。
忙等待
- 临界区的这段代码保证了各个进程是按照序号进行相加
- 这个等待消耗cpu资源
显式同步:互斥量(锁)
- 这个版本线程不一定按照从小到大的顺序来,使用互斥量的效率更高,先执行完的不需要等待
- 操作系统选择顺序,谁先执行完谁先来
- 被锁上的处于阻塞态,不占用cpu资源
- 锁只能保证一段时间内只有线程运行