首页 > 其他分享 >学习笔记7

学习笔记7

时间:2023-10-28 21:12:26浏览次数:28  
标签:int 笔记 学习 死锁 线程 pthread 进程 left

目录

知识点归纳

第4章 并行计算

并行性和并发性

并行算法只识别可并行执行的任务。CPU系统中,并发性是通过多任务处理来实现的。
通常,并行算法只识别可并行执行的任务,但是它没有规定如何将任务映射到处理组件。在理想情况下,并行算法中的所有任务都应该同时实时执行。然而,真正的并行执行只能在有多个处理组件的系统中实现,比如多处理器或多核系统。在单CPU系统中,一次只能执行一个任务。在这种情况下,不同的任务只能并发执行,即在逻辑上并行执行。在单CPU系统中,并发性是通过多任务处理来实现的

线程

1、线程的定义

线程是进程的基本执行单元,一个进程的所有任务都在线程中执行;

进程要想执行任务,必须得有线程,进程至少要有一条线程;

程序启动会默认开启一条线程,这条线程被称为主线程或UI线程;

2、进程的定义

进程是指在系统中正在运行的一个应用程序; 每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存;

3、进程与线程的关系

l 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间;

l 资源拥有:同一进程内的线程共享本进程的资源;如内存、I/O、CPU等,但是进程之间的资源是独立的;

l 健壮性:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃,整个进程都死掉。所以多进程要比多线程健壮;

l 进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程;

l 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制;

l 线程是处理器调度的基本单位,但进程不是;

  • 用线程计算矩阵的和
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define N 4
int A[N][N], sum[N];
void *func(void *arg)
{
    int j, row;
    pthread_t tid = pthread_self(); // get thread ID number 
    row = (int)arg;	// get row number from arg
    printf("Thread %d [%lu] computes sum of row %d\n", row, tid, row); 
    for (j=0; j<N; j++)
    {
        sum[row] += A[row][j];
    }
    printf("Thread %d [%lu] done sum[%d] = %d\n",row, tid, row, sum[row]);
    pthread_exit((void*)0); // thread exit: 0=normal termination
}

int main (int argc, char *argv[])
{
    pthread_t thread[N];	// thread IDs
    int i, j, r, total = 0;
    void *status;
    printf("Main: initialize A matrix\n");

    for (i=0; i<N; i++)
    {
        sum[i] = 0;
        for (j=0; j<N; j++)
        {
            A[i][j] = i*N + j + 1;
            printf("%4d" ,A[i][j]);
        }
    printf("\n");
    }
    printf("Main: create %d threads\n", N);
    for(i=0; i<N; i++)
    {
        pthread_create(&thread[i], NULL, func, (void *)i);
    }
    printf("Main: try to join with threads\n");
    for(i=0; i<N; i++) 
    {
        pthread_join(thread[i], &status);
        printf("Main: joined with %d [%lu]: status=%d\n",i, thread[i], (int)status);
    }
    printf("Main: compute and print total sum:"); 
    for (i=0; i<N; i++)
    {
        total += sum[i];
    }
    printf("tatal = %d\n", total); 
    pthread_exit(NULL);
}

线程的优点

① 线程创建和切换速度更快

② 线程的响应速度更快

③ 线程更适合并行计算

线程的缺点

① 由于地址空间共享,线程需要来自用户的明确同步。

② 许多库函数可能对线程不安全,例如传统strtok()函数将一个字符串分成一连串令牌。通常任何使用全局变量或依赖于静态内存内容的函数.线程都不安全。为了使库函数 适应线程环境,还需要做大量的工作。

③ 在单CPU系统上,使用线程解决问题实际上要比使用顺序程序慢,这是由在运行 时创建线程和切换上下文的系统开销造成的。

线程同步

由于线程在进程的同一地址空间中执行,它们共享同一地址空间中的所有全局变量和数据结构。当多个线程试图修改同一共享变量或数据结构时,如果修改结果取决于线程的执行顺序,则称之为竞态条件。在并发程序中,绝不能有竞态条件。否则,结果可能不一致。除了连接操作之外,并发执行的线程通常需要相互协作。为了防止出现竞态条件并且支持线程协作,线程需要同步。通常,同步是一种机制和规则,用于确保共享数据对象的完整性和并发执行实体的协调性。

  • 互斥量
    最简单的同步工具是锁,它允许执行实体仅在有锁的情况下才能继续执行,在Pthread 中,锁被称为互斥量,意思是相互排斥。
  • 死锁预防
    互斥量使用封锁协议。如果某线程不能获取互斥量,就会被阻塞,等待互斥量解锁后再继续。在任何封锁协议中,误用加锁可能会产生一些问题。最常见和突出的问题是死锁。死锁是一种状态,在这种状态下,许多执行实体相互等待,因此都无法继续下去。
    在这种情况下,T1和T2将永远相互等待,由于交叉加锁请求,它们处于死锁状态。与竞态条件类似,死锁决不能存在于并发程序中。有多种方法可以解决可能的死锁问题,其中包括死锁预防、死锁规避、死锁检测和恢复等。在实际系统中,唯一可行的方法是死锁预防,试图在设计并行算法时防止死锁的发生。一种简单的死锁预防方法是对互斥量进行排序,并确保每个线程只在一个方向请求互斥量,这样请求序列中就不会有循环。

但是,仅使用单向加锁请求来设计每个并行算法是不可能的。在这种情况下,可以使用条件加锁函数pthread_mutex_trylock()来预防死锁。如果互斥量已被加锁,则trylock()函数 会立即返回一个错误。在这种情况下,调用线程可能会释放它已经获取的一些互斥量以便进行退避,从而让其他线程继续执行。

  • 用线程快速排序
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define N 10
typedef struct{
    int upperbound;
    int lowerbound;
}PARM;

int A[N]={5,1,6,4,7,2,9,8,0,3};

int print()	// print current a[] contents
{
    int i;
    printf("[ ");
    for (i=0; i<N; i++)
    {
        printf("%d ", A[i]);
    }
    printf("]\n");
}

void *qsort_1(void *aptr)
{
    PARM *ap, aleft, aright;
    int pivot, pivotIndex, left, right, temp; 
    int upperbound, lowerbound;

    pthread_t me, leftThread, rightThread; 
    me = pthread_self();
    ap = (PARM *)aptr; 
    upperbound = ap->upperbound; 
    lowerbound = ap->lowerbound;
    pivot = A[upperbound]; 
    left = lowerbound - 1; 
    right = upperbound;
    if (lowerbound >= upperbound) 
        pthread_exit(NULL);
    
    while (left < right) 
    {
        do { left++;} while (A[left] < pivot);
            do { right--;}while (A[right] > pivot);
        if (left < right )
        {
            temp = A[left]; 
            A[left] = A[right];
            A[right] = temp;
        }
    }
    print();
    pivotIndex = left; 
    temp = A[pivotIndex]; 
    A[pivotIndex] = pivot; 
    A[upperbound] = temp; // start the "recursive threads" 
    aleft.upperbound = pivotIndex - 1;
    aleft.lowerbound = lowerbound; 
    aright.upperbound = upperbound; 
    aright.lowerbound = pivotIndex + 1; 
    printf("%lu: create left and right threads\n", me);
    pthread_create(&leftThread, NULL, qsort_1, (void *)&aleft);
    pthread_create(&rightThread, NULL, qsort_1, (void *)&aright);// wait for left and right threads 
    pthread_join(leftThread, NULL); 
    pthread_join(rightThread, NULL); 
    printf("%lu: joined with left & right threads\n", me);
}

int main(int argc, char *argv[])
{
    PARM arg;
    int i, *array; 
    pthread_t me, thread; 
    me = pthread_self();
    printf("main %lu: unsorted array =" ,me);
    print();
    arg.upperbound = N-1;
    arg.lowerbound = 0;
    printf("main %lu create a thread to do QS\n", me);
    pthread_create(&thread, NULL, qsort_1, (void *)&arg); // wait for QS thread to finish 
    pthread_join(thread, NULL);
    printf("main %lu sorted array = ", me); 
    print();
}


苏格拉底挑战



问题与解决方案

编程难度大:并行计算的编程模型通常比串行计算要复杂得多,需要考虑的问题也更多。建议从简单的例子入手,逐步掌握并行编程的基本技巧。
调试困难:由于并行计算涉及到多个处理器之间的交互,因此调试起来相对比较困难。建议使用专门的调试工具,如 PAPI、VTune 等,可以帮助我们定位问题所在。
硬件限制:并非所有的计算机都支持并行计算,而且即使支持,也可能因为硬件性能不足而导致实验效果不佳。建议尽量选择性能较好的机器进行实验。
编程语言的选择:不同的编程语言对于并行计算的支持程度不同,有的语言可能更适合进行并行计算。建议根据自己的需求选择合适的编程语言。
数据集的选择:不同的数据集对于并行计算的效果有很大影响,有时候即使是同样的算法,在不同的数据集上表现也会有所不同。建议选择合适的数据集进行实验。

实践过程

标签:int,笔记,学习,死锁,线程,pthread,进程,left
From: https://www.cnblogs.com/lizhuotong/p/17794639.html

相关文章

  • AJAX学习(四)-(axios核心的原理)
    一、Promise1.定义Promise对象用于表示一个异步操作的最终完成(或失败)及其结果值我们用一张图来清晰的看Promise位于哪里2.好处1.逻辑更清晰2.了解axios函数内部运作机制3.能解决回调函数地狱问题3.使用语法及步骤示例代码如下:<!DOCTYPEhtml><htmllang="en"><head><metacha......
  • 大数据的机器学习应用
    当谈到大数据时,机器学习扮演了至关重要的角色。它不仅能够处理庞大的数据集,还能从中提取有价值的信息,并为企业和组织提供深刻的洞察。本文将探讨大数据中机器学习的应用,以及如何使用Python实现一些基本的机器学习算法。什么是大数据的机器学习应用?大数据的机器学习应用是利用机器......
  • 20231327 司宏林《计算机基础与程序设计》第5周学习总结
    学期(2023-2024-1)学号(20231327)《计算机基础与程序设计》第5周学习总结作业信息这个作业属于哪个课程<班级的链接>(如2023-2024-1-计算机基础与程序设计)这个作业要求在哪里<作业要求的链接>(如2023-2024-1计算机基础与程序设计第5周作业)这个作业的目标<关于机器语......
  • 第八周Linux教材第四章学习笔记——并发编程
     第四章 并发编程4.1并行计算导论在早期,大多数计算机只有一个处理组件,称为处理器或中央处理器(CPU)。受这种硬件条件的限制,计算机程序通常是为串行计算编写的。要求解某个问题,先要设计一种算法,描述如何一步步地解决问题,然后用计算机程序以串行指令流的形式实现该算法。在只有......
  • 【深度学习 | 概念】那些深度学习路上必经的 常见问题解决方案及最佳实践,确定不来看看
    ......
  • #深度学习复现Github项目代码流程详细过程
    背景要求:已安装好anaconda及pycharm,这两个的安装可从网上学习安装,教程很多。第一步,在Github上下载项目代码因为第一次运行代码,找一些比较多运行成功的例子来练习,这次我找的是Github上的pix2pixGAN项目的源码,具体路径如下:https://github.com/junyanz/pytorch-CycleGAN-and-pix2......
  • 学习笔记7
    并发编程线程原理:一个操作系统(OS)包含许多并发进程。在进程模型中,进程是独立的执行单元。线程是某进程同一地址空间上的独立执行单元。创建某个进程就是在一个唯一地址空间创建一个主线程。当某进程开始时,就会执行该进程的主线程。如果只有一个主线程,那么进程和线程实际上并没......
  • 2023-2024-1 20231329《计算机程序与设计》第五周学习总结
    作业信息这个作业属于哪个课程https://edu.cnblogs.com/campus/besti/2023-2024-1-CFAP这个作业要求在哪里https://www.cnblogs.com/rocedu/p/9577842.html#WEEK05这个作业的目标计算机科学概论第6章并完成云班课测试《C语言程序设计》第4章并完成云班课测试......
  • 自制x86 BOOTLADER开发笔记(1)——— 开发环境配置
    前言数年前,出于对于操作系统内核的好奇和兴趣,看了一些自制内核资料和教程,断断续续地也写了一个简单的的玩具内核。在学习的过程中,往往第一步遇到的问题就是内核的加载和系统的引导,发现不少教程都使用grub等现成的工具直接完成这一步骤,这样能快速的完成读取硬盘、加载内核文件、......
  • Go语言数组与切片学习总结
    一.数组数组的定义:相同类型的数据集合go语言中数组的索引从0开始没有赋值的数值型数组,默认值为0数组一旦被创建,它的大小就是不可改变的(1)声明数组与打印var变量名[大小]变量类型//数组的声明varnums[4]int//数组的赋值nums[0]=1nums[1]=2nums[2]=......