并行程序设计-第四讲.Pthread编程


第四讲 多线程Pthread编程

共享内存系统和分布式内存模型回顾

伪共享

  1. cache按照行读取

  2. 当多个处理器访问同一行,即使访问的是不同的机器字,也会潜在竞争

  3. 会产生不必要的协同开销

    image-20221031143000351

    1. 当数据很少的时候,Core1和Core0访问的是同一行,同一个缓存行里的不同变量在同时被修改

共享内存编程

  1. 动态线程
    1. 主线程等待计算工作,fork新线程分配工作,工作线程完成任务后结束
    2. 资源利用率高
  2. 主线程完成时fork出所有线程
    1. 性能更优,但可能浪费系统资源

并行程序设计的复杂性

POSIX Threads编程

基本概念

线程库:

  • Pthread是POSIX标准
    • 相对底层
    • 可移植
  • OpenMP是新标准
    • 高层编程,适用于共享内存架构上的科学计算

POSIX Thread

image-20221031144319854

基础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执行

      • image-20221107141515838

        image-20221107141538026

同步

  • 例子 估算pai

    • image-20221107142015679

    • 多线程版本

      image-20221107142246876

      问题:每个线程都要把数加到sum上面,会存在竞争,结果是错误的

概念回顾

  • 原子性

    • 一组操作要么全部执行要么全不执行,则称其是原子性的
  • 临界区

    • 共享资源的代码段,一次只能允许一个线程执行该代码
  • 竞争条件

    • 多个线程/进程尝试更新同一个共享资源时,结果可能是无法预测的,则存在竞争
    • 如果存在竞争则创建临界区
  • 数据依赖

    • 两个内存的序。为了保证结果正确性,必须保持这个序
  • 同步

    • 时间上强制使各执行进程/线程在某一点必须互相等待,确保各进程/线程的正常顺序和对共享可写数据的正确访问。

忙等待

image-20221107143703393

  • 临界区的这段代码保证了各个进程是按照序号进行相加
  • 这个等待消耗cpu资源

显式同步:互斥量(锁)

image-20221107144116394

image-20221107144218073

  • 这个版本线程不一定按照从小到大的顺序来,使用互斥量的效率更高,先执行完的不需要等待
    • 操作系统选择顺序,谁先执行完谁先来
    • 被锁上的处于阻塞态,不占用cpu资源
  • 锁只能保证一段时间内只有线程运行
死锁

image-20221107150037798

使用信号量同步

使用barrier同步

image-20221107151930138


文章作者: zxn
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 zxn !
  目录