精华内容
下载资源
问答
  • 多核多线程复习

    2019-12-20 17:41:17
    北京科技大学天津学院17级多核多线程复习总结 lesson 1 1. 弗林分类 2. 进程与线程区别、线程状态 3. 片上多核处理器 4. 计算机硬件工艺发展顺序

    lesson1

    1. 多核的概念
      单芯片多处理器,简称CMP
      例题:
      为什么说从单核到多核的转变成为不可避免的历史趋势:(D)
      A. 指令级并行的局限
      B. 能耗与散热
      C. 内存墙问题
      D. 以上都是
    2. 弗林分类(缩写也要记住):
      单指令流单数据流(SISD)
      单指令流多数据流(SIMD)
      多指令流单数据流(MISD)
      多指令流多数据流(MIMD)
      例题:
      阵列处理机又称为并行处理机,它的体系结构属于_______计算机。(B)
      A.SISD
      B.SIMD
      C.MIMD
      D.MISD
      弗林(Flynn )根据指令流和数据流的不同组织方式,把计算机系统的结构进行了分类,以下属于弗林分类的是:(ABCD )
      A. 单指令流单数据流(Single Instruction stream Single Data stream – SISD)
      B. 单指令流多数据流(Single Instruction stream Multiple Data stream – SIMD)
      C. 多指令流单数据流(Multiple Instruction stream Single Data stream – MISD )
      D. 多指令流多数据流(Multiple Instruction stream Multiple Data stream – MISD )
      以下哪个不属于SIMD。(D)
      A. 并行处理机
      B. 阵列处理机
      C. 向量处理机
      D. 标量流水线处理机
    3. 进程与线程的区别、线程状态
      进程拥有独立的地址空间,而线程和其他线程共享进程的地址空间。
      进程之间的通信可以使用操作系统原语或通过共享存储空间来实现,而线程使用当前程序设计语言的原语或者通过进程共享空间来实现通信。
      进程上下文的切换是重量级的,进程所有状态都要保存。而线程之间的切换是轻量级的,只需要保存当前寄存器的状态
      例题:
      什么是线程、进程,它们之间的关系是什么?
      答:进程是一组离散的(执行)程序任务集合;线程是进程上下文中执行的代码序列,又被称为轻量级进程。进程中可包含一个或多个线程。
      对于进程,以下表述不正确的是:(D )
      A. 进程是指程序在一个数据集合上运行的过程
      B. 进程是系统进行资源分配和调度运行的一个独立单位
      C. 在操作系统中引入进程的目的,是为了使多个程序并发执行,以改善资源利用率及提高系统的吞吐量
      D. 在操作系统中引入进程的目的,是为了减少程序并发执行时所付出的时空开销,使操作系统具有更好的并发性
      对于线程,以下表述不正确的是:( C )
      A. 线程是进程中的一个实体,是被系统调度和分配的基本单元
      B. 每个程序至少包含一个线程,那就是主线程
      C. 线程自己只拥有很少的系统资源,且不可与同属一个进程的其他线程共享所属进程所拥有的全部资源
      D. 同一进程中的多个线程之间可以并发执行,从而更好地改善了系统资源的利用率
      以下表述不正确的是:( C )
      A. 人们习惯上称线程为轻量级进程(lightweight process, LWP ),线程是CPU 调度和分派的基本单元
      B. 在创建或撤消进程时,系统都要为之分配或回收资源
      C. 进程切换的开销也远小于线程切换的开销
      D. 线程切换只需保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作
      在操作系统中引入进程的目的是:(A )
      A. 提高系统吞吐量
      B. 减少程序并发执行时所付出的时空开销
      C. 便于组织程序逻辑
      D. 以上都正确
      在操作系统中引入线程的目的是:(B )
      A. 提高系统吞吐量
      B. 减少程序并发执行时所付出的时空开销
      C. 便于组织程序逻辑
      D. 以上都正确
      ______是CPU 调度和分派的基本单位,______是资源拥有的基本单位。(D)
      A. 线程,线程
      B. 进程,线程
      C. 进程,进程
      D. 线程,进程
    4. 进程状态及其转换
      进程状态及转换
      例题:
      对于线程生命周期,以下表述正确的是:(D )
      A. 系统中的每个线程都有一个从创建到消亡的过程,我们把这一过程称作该线程的生命周期。
      B. 线程生命周期包括创建、就绪状态、运行状态、等待状态和消亡
      C. 就绪状态的线程运行所需的一切条件都得到满足,已获得必要的资源和设备。但因处理器资源个数少于线程个数,所以该线程不能运行,而必须位于队列中等待分配处理器资源
      D. 以上都正确
    5. 片上多核处理器(单芯片多处理器)
      片上多核处理器(Chip Multi-Processor,CMP)就是将多个计算内核集成在一个处理器芯片中,从而提高计算能力。
    6. 计算机硬件工艺发展顺序
      电子管数字计算机、晶体管数字计算机、集成电路数字计算机、大规模集成电路数字计算机
      全世界第一台全自动电子数字计算机ENIAC 于哪一年研制成功?(B )
      A.1944 年
      B.1945 年
      C.1946 年
      D.1947 年
      计算机的硬件工艺发展顺序是:(A )
      A. 电子管数字计算机、晶体管数字计算机、集成电路数字计算机、大规模集成电路数字计算机
      B.晶体管数字计算机、电子管数字计算机、集成电路数字计算机、大规模集成电路数字计算机
      C.电子管数字计算机、集成电路数字计算机、大规模集成电路数字计算机、晶体管数字计算机
      D. 电子管数字计算机、集成电路数字计算机、晶体管数字计算机、大规模集成电路数字计算机

    lesson2

    1. 多核多线程开发流程
      多核多线程开发流程
      其中,第三步确定分解模式有:分任务、分数据、分数据流
      第五步选择并行模型有:Win32,OpenMP,MPI,TBB
    2. 分解模式
      任务分解、数据分解、数据流分解
      数据分解:减少数据的关联性 接触面积小点 (ppt第九页)
    3. 处理数据依赖的方法
      变量本地化(减少数据关联),改造变量,规约(前提:切片),明确的同步机制

    lesson3

    1. 句柄
      句柄是一种指向指针的特殊指针。Windows中的句柄实际上是一个唯一的数字,它引用一个Windows对象,例如窗口、图标等。
      Windows内存管理器在移动对象在内存中的位置后,把对象新的地址告知句柄地址来保存。这样只需记住句柄地址就可以间接知道对象具体在内存中的哪个位置。
      句柄地址(稳定)→记载着对象在内存中的地址→对象在内存中的地址(不稳定)→实际对象
      下面哪个说法是正确的(AB )
      A. 每个进程被初始化时,系统为它分配一个句柄表,用于保存该进程使用的内核对象信息
      B. 相同的句柄值在不同的进程中可能标识不同的内核对象
      C. 一个进程中止执行,它使用的内核对象也会被撤销
      D. 内核对象是由进程拥有的
    2. 线程执行函数
      记住函数头线程执行函数
    3. 创建线程、等待线程、等待多个线程、撤销线程等四个函数,涉及到的所有程序
      创建线程:CreateThread(NULL,0,helloFunc,NULL,0,NULL);
      等待一个线程:WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);
      如:WaitForSingleObject(hThread , INFINITE);
      等待多个线程:WaitForMultipleObjects(numThreads, hThread,TRUE, INFINITE);
      线程退出:BOOL CloseHandle(HANDLE hObject);
      例题:
      简述下列Windows多线程程序设计中常用函数的含义
      答案就是上边的那个
      分析以下程序可能出现的结果及原因
      #include <stdio.h>
      #include <windows.h>
      DWORD WINAPI helloFunc(LPVOID arg ) { 
      	printf(“Created thread says ‘Hello’ \n”); 
      	return 0; 
      }
      void main( ) {
      	HANDLE hThread = 
      		CreateThread(NULL, 0, helloFunc, 
                     			NULL, 0, NULL );
      	printf(“Main thread says ‘Hello’ \n”); 
      	CloseHandle(hThread);
      }
      
      结果:
      (1)Main thread says ‘Hello’
      Created thread says ‘Hello’
      (2)Created thread says ‘Hello’
      Main thread says ‘Hello’
      (3)Main thread says ‘Hello’
      情况三是主线程执行完之后就直接触发CloseHandle(hThread)方法
      期望的结果是情况二,如果想弄成这样的话,可以使用全局变量+轮循(ppt 14)或者WaitForSingleObject
      const int numThreads = 4;
      DWORD WINAPI threadFunc(LPVOID pArg) { 
      int* p = (int*)pArg;
      int myNum = *p;
      printf("Thread number %d\n", myNum);
      return 0;
      }
      void main() {
      	HANDLE hThread[numThreads];
      	for (int i = 0; i < numThreads; i++) {
        		hThread[i] = CreateThread(NULL, 0, threadFunc, &i, 0, NULL);
      	}
      	WaitForMultipleObjects(numThreads, hThread,
      					TRUE, INFINITE);
      }
      
      可能出现
      4444
      0123等
      出现这种结果是因为四个线程共享i,产生数据竞争,既读又写
      解决方法:变量本地化(ppt 24)
    4. win32线程同步的实现方法
      全局变量、事件、互斥量、临界区、信号量
      前两个处理先后顺序(同步)问题,后三个处理互斥问题,其中事件是专门处理先后顺序的,互斥量也可处理先后顺序
    5. 卖票的
      #include <windows.h>
      #include <iostream.h>
      
      DWORD WINAPI Fun1Proc(LPVOID lpParameter);
      DWORD WINAPI Fun2Proc(LPVOID lpParameter);
      
      int tickets=100;
      HANDLE hMutex;///
      
      void main()
      {
      	hMutex=CreateMutex(NULL,FALSE,NULL);///
      
      	HANDLE hThread1;
      	HANDLE hThread2;
      
      	hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
      	hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
      
      	HANDLE hThreads[2] = {hThread1, hThread2};
      	WaitForMultipleObjects(2,hThreads,TRUE,INFINITE);
      
      	CloseHandle(hThread1);
      	CloseHandle(hThread2);
      
      	CloseHandle(hMutex);///
      }
      
      DWORD WINAPI Fun1Proc(LPVOID lpParameter)
      {
      	while(TRUE)
      	{
      		WaitForSingleObject(hMutex,INFINITE);///
      		if(tickets>0)
      		{
      			Sleep(2);
      			cout<<"thread1 sell ticket : "<<tickets--<<endl;
      		}
      		else
      			break;
      		ReleaseMutex(hMutex);///
      
      	}
      	return 0;
      }
      
      DWORD WINAPI Fun2Proc(LPVOID lpParameter)
      {
      
      	while(TRUE)
      	{
      		WaitForSingleObject(hMutex,INFINITE);///
      		if(tickets>0)
      		{
      			Sleep(2);
      			cout<<"thread2 sell ticket : "<<tickets--<<endl;
      		}
      		else
      			break;
      		ReleaseMutex(hMutex);///
      
      	}
      	return 0;
      }
      

    lesson4

    1. OpenMp特征
      一种面向共享内存以及分布式共享内存的多处理器多线程并行编程语言。
      执行模型采用Fork-Join的形式
      以下表述错误的是:(D)
      A. OpenMP 可以根据目标系统尽量使用最优数量的线程个数
      B. 使用线程池可以避免为每个线程创建新进程的开销
      C. 线程池通常具有最大线程数限制,如果所有线程都繁忙,而额外的任务将放入队列中,直到有线程可用时才能够得到处理
      D.对于有优先级的线程,也可以使用线程池
      以下表述不正确的是:(D )
      A. OpenMP 的编程模型以线程为基础,通过编译制导语句来显示地制导并行化,为编程人员提供了对并行化的完整控制
      B. OpenMP 的执行模型采用Fork-Join 的形式
      C. Fork-Join 执行模式在开始执行的时候,只有一个叫做主线程的运行线程存在
      D. OpenMP 同时支持C/C++语言和Java 语言

    2. “helloworld”中涉及到的函数

      #include “omp.h”
      int main ( ){
      	printf(“Hello from serial.\n”);
      	//串行执行
      	printf(“Thread number = %d\n”,
      			omp_get_thread_num( ));  
      	omp_set_num_threads(4);
      	#pragma omp parallel //开始并行执行
      	{
      		printf(“Hello from parallel. Thread 	number=%d\n”,omp_get_thread_num( ));
      	}//没有,默认并行距离最近的语句结构
      	printf(“Hello from serial again.\n”);
      	return 0;
      }
      
    3. 求PI的程序

      OpenMP

      #include "stdafx.h"
      #include <omp.h>
      #include <time.h>
      long num_steps = 100000000;
      double step;
      int main(){
       	clock_t start, stop;
      	double x,pi,sum=0.0;
      	int i;
      	step = 1.0 /(double) num_steps;
      	start=clock();
      	omp_set_num_threads(4);
      	#pragma omp parallel for private(x) reduction( +:sum)
      		for(i=0;i<num_steps; i++){
      			x=(i+0.5)*step;
      			sum = sum+4.0/(1.0+x*x);
      		}
      		pi = sum*step;
      		stop= clock();
      		printf( "The value of PI is %15. 12f\n",pi);
      		printf("The time to calculate PI was %f seconds\n", ((double)(stop-start)/1000.0));
      		return 0:
      }
      
    4. 子句的掌握:parallel for reduction private barrier
      parallel 后续语句按多线程方式运行
      parallel for 后续的for循环语句按多线程方式运行
      parallel private 并行区变量私有化指导语句,说明后续语句中的某变量在多线程方式运行时被各线程私有化,每次仅容许一个线程访问的变量
      parallel reduction
      如:#pragma omp parallel for private(x) reduction(+:sum)
      每个线程运行都得到一个sum,所有线程都执行完后再把所得的所有sum加起来
      parallel barrier 用于并行区内代码的线程同步,所有线程执行到 barrier 时要停止,直到所有线程都执行到 barrier 时才继续往下执行。

    5. 任务调度的方法
      静态调度 static
      动态调度 dynamic
      guided调度
      runtime调度
      以下哪些不是OpenMP 的负载平衡调度方案:(D )
      A.static
      B.dynamic
      C.runtime
      D.public

    6. 程序性能分析实例
      高精度性能计数器:

      QueryPerformanceFrequency(
      		(LARGE_INTEGER*)&frequency);
      QueryPerformanceCounter(
      		(LARGE_INTEGER*)&counter);
      

    lesson6

    1. 非阻塞(CAS)算法的特征
      无阻塞 只要没有竞争,线程就可以持续执行
      无锁 系统整体持续执行
      无等待 每个线程都可以持续执行,即使遇到竞争
    2. 线程安全函数
      一个线程安全的函数通过加锁的方式来实现多线程对共享数据的安全访问。
      非可重入函数可能无法满足线程安全要求,不可用于多线程环境
      线程函数安全化:
      为共享资源加锁
      对于非线程安全函数:
      (1) 使用作用于整个函数库的锁
      (2) 使用作用于单个库组件/一组组件的锁
    3. 数据竞争是指在多线程程序中,不同线程对共享变量的访问没有特定的顺序,发生读写操作和写写操作。

    lesson7

    1. MPI 特征:
      MPI是一种消息传递编程模型,并成为这种编程模型的代表和事实上的标准。消息传递方式是广泛应用于多类并行机的一种模式,特别是分布存储并行机(分布式系统)。
    2. 6个接口函数概念和4个组通信接口概念
      6个接口函数
      (1)初始化:int MPI_Init(int *argc, char ***argv)
      MPI程序的第一个调用,完成MPI程序所有的初始化工作,将命令行参数传给各个进程。
      (2)获取当前进程标识
      int MPI_Comm_rank(MPI_Comm comm,int *rank)
      函数返回时,rank中存放当前进程在给定的通信域中的进程标识号。
      (3)获取通信域包含的进程数
      int MPI_Comm_size(MPI_Comm comm,int *size)
      函数返回时,size中存放指定通信域中的进程数。
      (4)消息发送
      源进程将缓存中的数据发送到目的进程。
      int MPI_Send(void *buf, int count, MPI_Datatype datatype, int dest, int tag,MPI_Comm comm)
      (5)MPI_Recv
      接收源进程source的消息,并且该消息的数据类型和消息标识和本API指定的datatype和tag相一致。
      接收到的消息所包含的数据元素的个数最多不能超过count个。若超过count个,则会发生溢出错误。若少于count个,则只有相应于这个消息的那些地址上的内容被修改。
      count可以是0,这种情况下的数据部分是空的
      (6)MPI程序的结束
      int MPI_ Finalize()
      MPI程序的最后一个调用,结束MPI程序的运行。它是MPI程序的最后一条可执行语句,否则程序的运行结果是不可预知的。
      四个通信接口
      (1)广播 一个任务给多个进程
      根进程将一条消息发送到组内的所有其它进程,同时也包括它本身在内。
      Int MPI_Bcast(void *buffer, int count, MPI_Datatype datatype, int root,MPI_Comm comm)
      广播
      (2)散发 分片
      根进程将数据的不同部分分别发送给各个进程。
      Int MPI_Scatter(void *sendbuf, int sendcount, MPI_Datatype data, void *recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm)
      散发
      (3)收集
      每个进程将自身发送缓冲区中的消息发送到根进程,根进程依据发送进程的进程标识号将它们各自的消息依次存放到自己的消息缓冲区中。
      Int MPI_Gather(void *sendbuf, int sendcount, MPI_Datatype data, void *recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm)
      (4)归约 收集时做的操作
      将每个进程缓冲区中的数据按给定的操作进行运算,并将计算结果返回到根进程的输出缓冲区中。
      Int MPI_Reduce(void *sendbuf, void *recvbuf, int count, MPI_Datatype data,MPI_Op op, int root, MPI_Comm comm)

    lesson8

    1. TBB的特征:是C++的扩展库

    补充

    1. Flynn将计算机划分为四种基本类型,即SISDSIMDMISDMIMD
    2. 线程的状态分为就绪、阻塞和运行状态。
    3. 一组进程(线程)中的每个进程(线程)均等待此组进程(线程)中某一其它进程(线程) 所占有的,因而永远无法得到的资源,这种现象称作死锁
    4. 分解足将应用程序划分成多个独立的任务,并确定这些任务之间的相互关系的过程,分解方 式包括数据分解、任务分解和数据流分解。
    5. 片上多核处理器的英文缩写是CMP
    6. 独立于体系结构性能优化方法主要有:避免冗余的函数调用避免不必要的边界检查变量本地化

    讲解音频:链接: https://pan.baidu.com/s/1qmKvw0QoG465zGi6_d3zOg 提取码: 9ax3
    PPT及相关复习资料:冰炫上有

    展开全文
  • 多核多线程同步

    千次阅读 2012-10-21 01:14:15
    1.2.6 多核多线程同步 本书多线程的实现主要是在Windows平台上,因此WinThread和OpenMP是最主要的实现。多线程实现的核心是并行,并行的难点是同步。因为往往会导致程序出错或者效率不高。 那么什么是同步?...

    from: http://book.51cto.com/art/201009/224283.htm

    1.2.6  多核多线程同步

    本书多线程的实现主要是在Windows平台上,因此WinThread和OpenMP是最主要的实现。多线程实现的核心是并行,并行的难点是同步。因为往往会导致程序出错或者效率不高。

    那么什么是同步?我们知道多线程的程序设计中需要实现多个线程共享同一段代码,这时,由于线程和线程之间互相竞争CPU资源,会使得线程无序地访问这些共享资源,最终可能导致无法得到正确的结果,这就是竞争。为了保证数据的一致性,避免对共享数据的访问造成数据的不一致性和错误,就需要同步。

    同步方法和同步结构有很多,包括但不限于:

    互斥量;

    原子操作;

    临界区;

    路障;

    事件;

    信号量。

    互斥是指在同一个时间,同一个地点不能有两件事情同时存在。作为互斥设备,有两个状态:上锁和空闲。同一个时刻只能有一个线程能对互斥量加锁。对于一个已经被加锁的互斥量,另外一个线程试图对它加锁时,该线程会被阻塞,直到该互斥量被释放。在WinThread编程中实现时,主要使用mutex;在OpenMP中,类似的实现如omp_lock_t。

    原子操作是指执行指令过程中不可分割的最小单元。比如声明变量的操作、给变量直接赋值的操作,这些都是原子操作,这些操作是安全的。在多线程的程序中,一旦将某         个关键代码封装成一个原子操作,那么对它们的操作就不会存在不同步的情况。因此原            子操作或者不执行,或者不分开的执行,这种操作保证了数据的完备性。在WinThread编程中,原子操作有Interlocked函数,例如InterlockedIncrement、InterlockedDeccrement、InterlockedExchange等; 在OpenMP中,类似的实现如#pragma omp atomic。

    临界区是一种多线程必须互斥访问的代码段,保证了原子性。一般都是在用户级线程实现的,开销较小。临界区的大小会影响性能,应尽可能分割成小块,提高并行度。在WinThread编程中,用EnterCriticalSection和LeaveCriticalSection实现,在OpenMP中,用#pragma omp critical来实现。

    路障是另外一种同步机制。主要用于逻辑计算的控制流操作。通过这种方法,任何线程在一个控制点等待其他所有线程在这个点完成,然后继续下面的处理。这种方法保证了任何一个线程不会在其他线程还没有到达前往下进行。路障的性能因素要考虑,包括线程数的多少和粒度的大小。过细的粒度,或者负载不均衡的多线程,会导致路障有很差的性能。在OpenMP中,路障大部分是隐式实现的,如#pragma omp parallel for在循环结束时会有线程的同步、#pragma omp single、#pragma omp section也类似,还有专门的语句#pragma omp barrier。

    有时线程并不是要访问某个受保护的数据或资源,而是需要等待某一事件(event)的发生,这在GUI编程中十分常见。事件主要在WinThread中实现,事件用于一个线程通知另一线程某一事件的发生(发信号表示某一操作已经完成),它是同步对象中形式最简单的一种,而且其同步的机制也是最具有弹性的。事件存在两种状态:激发状态和未激发状态(Signal/True、Unsignal/False)。事件可分为两类,即手动设置与自动恢复;前者需要程序手动设置,如SetEvent以及ResetEvent;后者指一旦事件发生并处理后,自动恢复到没有事件状态。

    信号量(Semaphore)也是WinThread的常见同步结构。它也是一个核心对象,拥有计数器,用来管理大量有限的系统资源。当计数器大于零时,信号量为有信号状态;当计数器为零时,信号量处于无信号状态。当线程用信号量对象的句柄作参数来调用等待函数WaitForSingleObject时,系统会检查该信号量所对应的资源数是否大于0(即资源是否可用),若大于0(称为有信号signaled)则减少资源计数并唤醒线程,若等于0(称为无信号nonsignaled)则让线程进入睡眠状态直到(超出指定时间或当指定时间为无限时)占用该资源的其他线程释放资源并增加资源的计数(使信号量大于0,即有信号或有资源可用)为止。通过创建信号量,等待多个对象以及释放信号量方式实现同步。信号量的本质就是PV操作,对应了两个原子操作。典型应用场景是多线程下保护共享资源的使用。例如有n个共享资源,一开始信号量的值是n,当一个线程需要获取一个资源时,就做一个P操作,当资源使用完毕后做一个V操作。

    展开全文
  •  虽说多线程是并发的,但如遇到两个线程要读写同一个数据,为了数据安全,必然是一个线程先对数据进行操作另一个线程在进行操作。这样在数据部分多线程实际上是串行化运行。  第一个解决方法是 无锁算法 ,但...

    一、帮助测试的软件

    1. Intel VTune性能分析器

           Intel VTune性能分析器可以帮助定位程序中与性能有关的问题。其在Windows下支持图形化界面,可支持命令行输入。主要功能有:

            取样功能可以帮助开发者定位程序中最消耗时间的函数和模块;

             曲线图可确定程序运行时函数调用顺序和显示关键路径;

             计数器监控器确定是否会因为可用内存减少或文件输入/输出而导致程序速度变慢;

             调优助手自动推荐代码改进方法。

    2. MKL数学核心函数库

             该库利用Intel多核处理器,提供高度优化的函数,使程序获得更高性能并减少开发时间。

    3. Thread Checker线程检查器

              快速查找和修复Windows和openMP软件中的线程bug(比如数据竞争),提示同线程错误相关的源代码位置。

     

    二、与体系无关的优化方法

    1.编译优化选项

               对于同一个函数来说,调用编译器不同的优化选项,产生的结果可能不同。

    编译选项函数1读写次数函数2读写次数读写所用时间
    -g8 read,2 write6 read,2 write0.21e-3/0.21e-3
    -O21 read,1 write1 read,0 write0.3e-3/0.7e-5
    -O32 read,1 write2 read,1 write0.27e-3/0.27e-3

           从中可看出,-O3不一定比-O2好,-O3采用inline技术,将函数直接嵌入main函数中,这样是否能就得看程序具体情况。

    2.减少不必要的内存存取

    void combine(data_t *dest)
    {
         int length = vec_length(v);
          for(int i=0;i<length;i++)
              *dest += data[i];
    }

            在combine函数中,*dest函数属于传入参数,是通过堆栈被函数体内部引用的。因此无论是读写,都要进入系统内存,成本过高。其实只要用一个局部变量,存储中间计算结果,最后传给dest就行了。

    3.函数调用的边际效应

    int counter = 0;
    int f(int x)
    {
       return counter++;
    }
    
    int func1(x)
    {
        return f(x)+f(x);
    }

     

    int counter = 0;
    int f(int x)
    {
       return counter++;
    }
    
    int func2(x)
    {
        return 2*f(x);
    }

     

           只要函数调用过程中改变了某些全局变量的值,就称函数调用有边际效应。存在边际效应的函数因调用次数对程序有不同影响。减少边际效应的方法就是 减少全局变量的使用。

    4.数据结构

            C语言中16位int数据结构,其数据范围在-32768~32767。但实际使用时,用户可能只用int来定义一个枚举类型。

     

    三、多线程编程常见问题

    1.串行化

            虽说多线程是并发的,但如遇到两个线程要读写同一个数据,为了数据安全,必然是一个线程先对数据进行操作另一个线程在进行操作。这样在数据部分多线程实际上是串行化运行。

            第一个解决方法是无锁算法,但过于复杂,而且容易出错。

            第二个解决方法是原子操作,本质上没有解决串行化问题,但可以让串行化速度提升。可惜目前厂商提供的原子操作有限。

            第三个解决方法是从设计层面来缩小串行化比例。使用任务分解模式、数据分解模式、数据共享模式等。

    注:运行在双CPU或四CPU上的程序,由于锁竞争导致的加速系数下降现象不明显。但随着CPU核数增多,这个问题会变得很严重。多核编程与多任务编程是不一样的。

    2.线程过多

           将给定工作量划分给过多线程,会造成线程启动和终止的开销比实际运行的开销更大。而且,过多的会导致共享有限硬件资源的开销增大。

           建议使用线程池。

    3.数据竞争与死锁

     

    四、非阻塞算法

            非阻塞算法的本质是停止一个线程执行不会阻碍其他执行实体的运行,其特点在于:

             1)无阻塞:没有竞争,线程就可以持续执行。

             2)无锁:系统整体持续运行。

             3)无等待:每个线程都可以执行,即使遇到竞争也是如此。只有极少算法(实现这一点)。

    1.CAS操作

             一种基本的非阻塞算法叫做比较并交换(CAS),其包含三个数:内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相同,那么将该位置的值更新为新值。

    int test()
    {
        int v;
        v = value.get();
        while(!value.compareAndSet(v, v+1));
        return v+1;
    }

           上面是使用该方法的一个计数器例子:将一个变量更新为新值,但如果从我上次看到这个变量之后其他线程修改了它的值,那么更新失败,重新再来。

            在轻度和中度竞争情况下,非阻塞算法的性能会超过阻塞算法,因为CAS的大多数时间在第一次尝试时就成功,而竞争时开销也不涉及线程挂起,只多了几个循环迭代;在高度竞争情况下,基于锁的算法提供比非阻塞算法更好的吞吐率。

    2.ABA问题

            在进行CAS操作时,主要看内存位置V的值A是否改变,但有一种情况:内存位置V的值A先改变了成了B,又从B变回了A。这种情况CAS算法便会产生问题。这类问题成为ABA问题。

            要解决该类问题,通常将标记与要进行CAS操作的每个值相关联,并原子地更新值和标记。将ABA问题转化为ABA'。

    3.cache乒乓现象

            如果将锁竞争分布到多个锁上,并且每个锁能够保证在其线程完成操作之前没有线程能够访问它所保护的cache线,那么它比等价的非阻塞算法性能要好。

     

    五、openMP编程

    1.openMP简介

            openMP是一种面向共享内存以及分布式共享内存的多处理器多线程编程语言。其执行模型为fork-join形式:开始执行时,只有一个主线程运行,当遇到需要并行计算时,派生出fork来执行任务,任务结束后派生线程退出,只剩下主线程运行。

             该模型通过编译制导语句来执行。编译制导语句是指在编译器编译程序时,识别特定注释。例如在C/C++语句中,用#pragma  omp  parallel来标志后面一段为并行程序块。

    展开全文
  • 这篇文章详细剖析了为什么在多核时代进行多线程编程时需要慎用volatile关键字。 主要内容有: 1. C/C++中的volatile关键字 2. Visual Studio对C/C++中volatile关键字的扩展 3. Java/.NET中的volatile关键字 4. ...

    这篇文章详细剖析了为什么在多核时代进行多线程编程时需要慎用volatile关键字。

    主要内容有:
    1. C/C++中的volatile关键字
    2. Visual Studio对C/C++中volatile关键字的扩展
    3. Java/.NET中的volatile关键字
    4. Memory Model(内存模型)
    5. Volatile使用建议

    1. C/C++中的volatile关键字

    1.1 传统用途

    C/C++作为系统级语言,它们与硬件的联系是很紧密的。volatile的意思是“易变的”,这个关键字最早就是为了针对那些“异常”的内存操作而准备的。它的效果是让编译器不要对这个变量的读写操作做任何优化,每次读的时候都直接去该变量的内存地址中去读,每次写的时候都直接写到该变量的内存地址中去,即不做任何缓存优化。它经常用在需要处理中断的嵌入式系统中,其典型的应用有下面几种:

    a. 避免用通用寄存器对内存读写的优化。编译器常做的一种优化就是:把常用变量的频繁读写弄到通用寄存器中,最后不用的时候再存回内存中。但是如果某个内存地址中的值是由片外决定的(例如另一个线程或是另一个设备可能更改它),那就需要volatile关键字了。(感谢Kenny老师指正)
    b.硬件寄存器可能被其他设备改变的情况。例如一个嵌入式板子上的某个寄存器直接与一个测试仪器连在一起,这样在这个寄存器的值随时可能被那个测试仪器更改。在这种情况下如果把该值设为volatile属性的,那么编译器就会每次都直接从内存中去取这个值的最新值,而不是自作聪明的把这个值保留在缓存中而导致读不到最新的那个被其他设备写入的新值。
    c. 同一个物理内存地址M有两个不同的内存地址的情况。例如两个程序同时对同一个物理地址进行读写,那么编译器就不能假设这个地址只会有一个程序访问而做缓存优化,所以程序员在这种情况下也需要把它定义为volatile的。

    1.2 多线程程序中的错误用法

    看到这里,很多朋友自然会想到:恩,那么如果是两个线程需要同时访问一个共享变量,为了让其中两个线程每次都能读到这个变量的最新值,我们就把它定义为volatile的就好了嘛!我想这个就是多线程程序中volatile之所以引起那么多争议的最大原因。可惜的是,这个想法是错误的。

    举例来说,想用volatile变量来做同步(例如一个flag)?错!为什么?很简单,虽然volatile意味着每次读和写都是直接去内存地址中去操作,但是volatile在C/C++现有标准中即不能保证原子性(Atomicity)也不能保证顺序性(Ordering),所以几乎所有试图用volatile来进行多线程同步的方案都是错的。我之前一篇文章介绍了Sequential Consistency模型(后面简称SC),它其实就是我们印象中多线程程序应该有的执行顺序。但是,SC最大的问题是性能太低了,因为CPU/编译器完全没有必要严格按代码规定的顺序(programorder)来执行每一条指令。学过体系结构的同学应该知道不管是编译器也好CPU也好,他们最擅长做的事情就是帮你做乱序优化。在串行时代这些乱序优化对程序员来说都是透明的,封装好了的,你不用关心它们到底给你乱序成啥样了,因为它们会保证优化后的程序的运行结果跟你写程序时预期的结果是一模一样的。但是进入多核时代之后,CPU和编译器还会继续做那些串行时代的优化,更重要的是这些优化还会打破你多线程程序的SC模型语义,从而使得多线程程序的实际运行结果与我们所期待的运行结果不一致!

    拿X86来说,它的多核内存模型没有严格执行SC,即属于weak ordering(或者叫relaxordering?)。它唯一允许的乱序优化是可以把对不同地址的load操作提到store之前去(即把store x->loady乱序优化成load y -> store x)。而store x -> store y、load x -> loady,以及store x -> loady不允许交换执行顺序。在X86这样的内存模型下,volatile关键字根本就不能保证对不同volatile变量x和y的store x-> load y的操作不会被CPU乱序优化成load y -> store x。

    而对多线程读写操作的原子性来说,诸如volatile x=1这样的写操作的原子性其实是由X86硬件保证的,跟volatile没有任何关系。事实上,volatile根本不能保证对没有内存对齐的变量(或者超出机器字长的变量)的读写操作的原子性。

    为了有个更直观的理解,我们来看看CPU的乱序优化是如何让volatile在多线程程序中显得如此无力的。下面这个著名的Dekker算法是想用flag1/2和turn来实现两个线程情况下的临界区互斥访问。这个算法关键就在于对flag1/2和turn的读操作(load)是在其写操作(store)之后的,因此这个多线程算法能保证dekker1和dekker2中对gSharedCounter++的操作是互斥的,即等于是把gSharedCounter++放到临界区里去了。但是,多核X86可能会对这个store->load操作做乱序优化,例如dekker1中对flag2的读操作可能会被提到对flag1和turn的写操作之前,这样就会最终导致临界区的互斥访问失效,而gSharedCounter++也会因此产生datarace从而出现错误的计算结果。那么为什么多核CPU会对多线程程序做这样的乱序优化呢?因为从单线程的视角来看flag2和flag1、turn是没有依赖关系的,所以CPU当然可以对他们进行乱序优化以便充分利用好CPU里面的流水线(想了解更多细节请参考计算机体系结构相关书籍)。这样的优化虽然从单线程角度来讲没有错,但是它却违反了我们设计这个多线程算法时所期望的那个多线程语义。(想要解决这个bug就需要自己手动添加memorybarrier,或者干脆别去实现这样的算法,而是使用类似pthread_mutex_lock这样的库函数,后面我会再讲到这点)

    当然,对不同的CPU来说他们的内存模型是不同的。比如说,如果这个程序是在单核上以多线程的方式执行那么它肯定不会出错,因为单核CPU的内存模型是符合SC的。而在例如PowerPC,ARM之类的架构上运行结果到底如何就得去翻它们的硬件手册中内存模型是怎么定义的了。

    2. Visual Studio对C/C++中volatile关键字的扩展

    虽然C/C++中的volatile关键字没有对ordering做任何保证,但是微软从Visual Studio2005开始就对volatile关键字添加了同步语义(保证ordering),即:对volatile变量的读操作具有acquire语义,对volatile变量的写操作具有release语义。Acquire和Release语义是来自data-race-free模型的概念。为了理解这个acquire语义和release语义有什么作用,我们来看看MSDN中的一个例子

    例子中的 while (Sentinel) Sleep(0); // volatile spin lock是对volatile变量的读操作,它具有acquire语义,acquire语义的隐义是当前线程在对sentinel的这个读操作之后的所有的对全局变量的访问都必须在该操作之后执行;同理,例子中的Sentinel = false; // exit critical section是对volatile变量的写操作,它具有release语义,release语义的隐义是当前线程在对sentinel这个写操作之前的所有对全局变量的访问都必须在该操作之前执行完毕。所以ThreadFunc1()读CriticalData时必定已经在ThreadFunc2()执行完CriticalData++之后,即CriticalData最后输出的值必定为1。建议大家用纸画一下acquire/release来加深理解。一个比较形象的解释就是把acquire当成lock,把release当成unlock,它俩组成了一个临界区,所有临界区外面的操作都只能往这个里面移,但是临界区里面的操作都不能往外移,简单吧?

    其实这个程序就相当于用volatile变量的acquire和release语义实现了一个临界区,在临界区内部的代码就是Sleep(2000); CriticalData++;或者更贴切点也可以看成是一对pthread_cond_wait和pthread_cond_signal。

    这个volatile的acquire和release语义是VS自己的扩展,C/C++标准里是没有的,所以同样的代码用gcc编译执行结果就可能是错的,因为编译器/CPU可能做违反正确性的乱序优化。Acquire和release语义本质上就是为了保证程序执行时memoryorder的正确性。但是,虽然这个VS扩展使得volatile变量能保证ordering,它还是不能保证对volatile变量读写的原子性。事实上,如果我们的程序是跑在X86上面的话,内存对齐了的变量的读写的原子性是由硬件保证的,跟volatile没有任何关系。而像volatileg_nCnt++这样的语句本身就不是原子操作,想要保证这个操作是原子的,就必须使用带LOCK语义的++操作,具体请看我这篇文章

    另外,VS生成的volatile变量的汇编代码是否真的调用了memorybarrier也得看具体的硬件平台,例如x86上就不需要使用memorybarrier也能保证acquire和release语义,因为X86硬件本身就有比较强的memory模型了,但是Itanium上面VS就会生成带memory barrier的汇编代码。具体可以参考这篇

    但是,虽然VS对volatile关键字加入了acquire/release语义,有一种情况还是会出错,即我们之前看到的dekker算法的例子。这个其实蛮好理解的,因为读操作的acquire语义不允许在其之后的操作往前移,但是允许在其之前的操作往后移;同理,写操作的release语义允许在其之后的操作往前移,但是不允许在其之前的操作往后移;这样的话对一个volatile变量的读操作(acquire)当然可以放到对另一个volatile变量的写操作(release)之前了!Bug就是这样产生的!下面这个程序大家拿VisualStudio跑一下就会发现bug了(我试了VS2008和VS2010,都有这个bug)。多线程编程复杂吧?希望大家还没被弄晕,要是晕了的话也很正常,仔仔细细重新再看一遍吧:)

    想解决这个Bug也很简单,直接在dekker1和dekker2中对flag1/flag2/turn赋值操作之后都分别加入full memory barrier就可以了,即保证load一定是在store之后执行即可。具体的我就不详述了。

    3. Java/.NET中的volatile关键字

    3.1 多线程语义

    Java和.NET分别有JVM和CLR这样的虚拟机,保证多线程的语义就容易多了。说简单点,Java和.NET中的volatile关键字也是限制虚拟机做优化,都具有acquire和release语义,而且由虚拟机直接保证了对volatile变量读写操作的原子性。(volatile只保证可见性,不保证原子性。java中,对volatile修饰的long和double的读写就不是原子的(http://java.sun.com/docs/books/jvms/second_edition/html/Threads.doc.html#22244),除此之外的基本类型和引用类型都是原子的。– 多谢liuchangit指正) 这里需要注意的一点是,Java和.NET里面的volatile没有对应于我们最开始提到的C/C++中对“异常操作”用volatile修饰的传统用法。原因很简单,Java和.NET的虚拟机对安全性的要求比C/C++高多了,它们才不允许不安全的“异常”访问存在呢。

    而且像JVM/.NET这样的程序可移植性都非常好。虽然现在C++1x正在把多线程模型添加到标准中去,但是因为C++本身的性质导致它的硬件平台依赖性很高,可移植性不是特别好,所以在移植C/C++多线程程序时理解硬件平台的内存模型是非常重要的一件事情,它直接决定你这个程序是否会正确执行。

    至于Java和.NET中是否也存在类似VS 2005那样的bug我没时间去测试,道理其实是相同的,真有需要的同学自己应该能测出来。好像这篇InfoQ的文章中显示Java运行这个dekker算法没有问题,因为JVM给它添加了mfence。另一个臭名昭著的例子就应该是Double-Checked Locking了。

    3.2 volatile int与AtomicInteger区别

    Java和.NET中这两者还是有些区别的,主要就是后者提供了类似incrementAndGet()这样的方法可以直接调用(保证了原子性),而如果是volatilex进行++操作则不是原子的。increaseAndGet()的实现调用了类似CAS这样的原子指令,所以能保证原子性,同时又不会像使用synchronized关键字一样损失很多性能,用来做全局计数器非常合适。

    4. Memory Model(内存模型)

    说了这么多,还是顺带介绍一下MemoryModel吧。就像前面说的,CPU硬件有它自己的内存模型,不同的编程语言也有它自己的内存模型。如果用一句话来介绍什么是内存模型,我会说它就是程序员,编程语言和硬件之间的一个契约,它保证了共享的内存地址里的值在需要的时候是可见的。下次我会专门详细写一篇关于它的内容。它最大的作用是取得可编程性与性能优化之间的一个平衡。

    5. volatile使用建议

    总的来说,volatile关键字有两种用途:一个是ISOC/C++中用来处理“异常”内存行为(此用途只保证不让编译器做任何优化,对多核CPU是否会进行乱序优化没有任何约束力),另一种是在Java/.NET(包括Visual Studio添加的扩展)中用来实现高性能并行算法(此种用途通过使用memorybarrier保证了CPU/编译器的ordering,以及通过JVM或者CLR保证了对该volatile变量读写操作的原子性)。

    一句话,volatile对多线程编程是非常危险的,使用的时候千万要小心你的代码在多核上到底是不是按你所想的方式执行的,特别是对现在暂时还没有引入内存模型的C/C++程序更是如此。安全起见,大家还是用Pthreads,Java.util.concurrent,TBB等并行库提供的lock/spinlock,conditional variable, barrier, AtomicVariable之类的同步方法来干活的好,因为它们的内部实现都调用了相应的memory barrier来保证memoryordering,你只要保证你的多线程程序没有datarace,那么它们就能帮你保证你的程序是正确的(是的,Pthreads库也是有它自己的内存模型的,只不过它的内存模型还些缺点,所以把多线程内存模型直接集成到C/C++中是更好的办法,也是将来的趋势,但是C++1x中将不会像Java/.NET一样给volatile关键字添加acquire和release语义,而是转而提供另一种具有同步语义的atomic variables,此为后话)。如果你想实现更高性能的lock free算法,或是使用volatile来进行同步,那么你就需要先把CPU和编程语言的memory model搞清楚,然后再时刻注意Atomicity和Ordering是否被保证了。(注意,用没有acquire/release语义的volatile变量来进行同步是错误的,但是你仍然可以在C/C++中用volatile来修饰一个不是用来做同步(例如一个eventflag)而只是被不同线程读写的共享变量,只不过它的新值什么时候能被另一个线程读到是没有保证的,需要你自己做相应的处理)

    Herb Sutter 在他的那篇volatile vs. volatile中对这两种用法做了很仔细的区分,我把其中两张表格链接贴过来供大家参考:

    volatile的两种用途
    volatile两种用途的异同

    最后附上《Java Concurrency in Practice》3.1.4节中对Java语言的volatile关键字的使用建议(不要被英语吓到,这些内容确实对你有用,而且还能顺便帮练练英语,哈哈):

    So from a memory visibility perspective, writing a volatile variableis like exiting a synchronized block and reading a volatile variable islike entering a synchronized block. However, we do not recommendrelying too heavily on volatile variables for visibility; code thatrelies on volatile variables for visibility of arbitrary state is morefragile and harder to understand than code that uses locking.

    Use volatile variables only when they simplify implementing andverifying your synchronization policy; avoid using volatile variableswhen veryfing correctness would require subtle reasoning aboutvisibility. Good uses of volatile variables include ensuring thevisibility of their own state, that of the object they refer to, orindicating that an important lifecycle event (such as initialization orshutdown) has occurred.

    Locking can guarantee both visibility and atomicity; volatile variables can only guarantee visibility.

    You can use volatile variables only when all the following criteria are met:
    (1) Writes to the variable do not depend on its current value, or youcan ensure that only a single thread ever updates the value;
    (2) The variable does not participate in invariants with other state variables; and
    (3) Locking is not required for any other reason while the variable is being accessed.

    参考资料

    1. 《Java Concurrency in Practice》3.1.4节
    2. volatile vs. volatile(Herb Sutter对volatile的阐述,必看)
    3. The “Double-Checked Locking is Broken” Declaration
    4. Threading in C#
    5. Volatile: Almost Useless for Multi-Threaded Programming
    6. Memory Ordering in Modern Microprocessors
    7. Memory Ordering @ Wikipedia
    8. 内存屏障什么的
    9. The memory model of x86
    10. VC 下 volatile 变量能否建立 Memory Barrier 或并发锁
    11. Sayonara volatile(Concurrent Programming on Windows作者的文章 跟我观点几乎一致)
    12. Java 理论与实践: 正确使用 Volatile 变量
    13. Java中的Volatile关键字

    冠诚, IBM中国研究院, 研究员
    任何与多核、并行、多线程有关的话题都可以找我聊聊:-) 我的邮箱是chenguancheng AT gmail.com

    展开全文
  • 多线程多核下“”的应用

    千次阅读 2011-11-21 19:23:47
    假设这样一种情况:有线程(或多核)需要在共享数据A满足某一条件时,对A进行操作. 以下举例两种实现 Fun_1() { lock()--------------1.1  Result=Check(A)-----1.2  Unlock()------------1.3  --------...
  • [转]《英特尔多核/多线程技术》

    千次阅读 2010-02-25 20:51:00
    Technorati 标签: 多核,多线程,intel ... 为了使大学生或软件开发人员在面对多核体系结构,以及需要多线程编程时有一本好的参考手册,同时为高等学校计算机专业的师生进行多核多线程程序教学时提供一本有价值的参考书,
  • 转自:http://www.cnblogs.com/skying555/p/6527189.htmlGIL 与 Python 线程的纠葛GIL 是什么东西?它对我们的 python 程序会产生什么样的影响?我们先来看一个问题。运行下面这段 python 程序,CPU 占用率是多少?#...
  • 多核基本意味着多线程,那么在多线程处理中有一个比较棘手的问题:当存在一些常驻的线程访问的共享数据时,退出时必须先结束这些常驻线程才能对共享资料进行释放操作。否则,先释放这些共享资源,后面的常驻线程访问...
  • 学习回顾 1.1 线程内存模型 1.2 线程池 1.3 信号量、事件驱动的服务器 1.4 多核执行多线程 1.5 线程安全函数 1.6 可重入函数 明日目标 0.目标完成情况 又摸了几天的鱼,终于完成了3天前的目标,再加了一点 牛客两道...
  • GIL 与 Python 线程的纠葛GIL 是什么东西?它对我们的 python 程序会产生什么样的影响?我们先来看一个问题。运行下面这段 python 程序,CPU 占用率是多少?# 请勿在工作中模仿,危险:)def dead_loop():while True:...
  • 为什么python的多线程不能利用多核CPU,但是多线程的确是在并发,而且还比单线程快 python 为什么不能利用多核CPUGIL其实是因为在python中有一个GIL(Global Interpreter Lock),中文为:全局解释器。 1、...
  • 点击上方蓝色小字,关注“涛哥聊Python”重磅干货,第一时间送达来源:后端技术指南针1.全局解释如题: Python的多线程为什么不能利用多核处理器?全局解释器(Global In...
  • Threading多线程什么是多线程线程的添加join功能QueueGIL线程安全与Lock 什么是多线程 线程是操作系统能够进行运算调度的最小单位;它被包含在进程之中,是进程中的实际运作单位。 多线程,是指从软件或者硬件上...
  • 单核处理器、多核处理器、多处理器与多线程编程 一.进程、线程、单核处理器  进程和线程都是操作系统的概念。进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成,即...
  • python 为什么不能利用多核CPU GIL其实是因为在python中有一个GIL(Global Interpreter Lock),中文为:全局解释器。...(在单核CPU下的多线程其实都只是并发,不是并行,并发和并行从宏观上来...
  • 实 验 报 告 ( 2016 / 2017 学年 第 二 学期) ...Windows环境下多线程程序设计|| 实验时间 2017 年 5 月 16 日 指导单位 计算机学院软件工
  • 1、python多线程为什么不能利用多核CPU? 2、为什么说“Python下多线程是鸡肋,推荐使用多进程!” 需要了解的背景知识: 1、GIL GIL 的全称为Global Interpreter Lock,意即全局解释器。在 Python 语言的主流...
  • python 为什么不能利用多核 CPUGIL 其实是因为在 python中...2、每个 CPU在同一时间只能执行一个线程:(在单核 CPU下的多线程其实都只是并发,不是并行,并发和并行从宏观上来讲都是同时处理多路请求的概念。 但并...
  • 多线程 并发 同步 原子
  • 前一篇提到,多线程会有线程安全问题。而解决这个问题的方案,就是本文要讲的: 线程安全中最常见的aba问题,其本质原因在于不同的线程对同一个资源有争抢行为,那么自然而然,解决这个问题的方式,就是限制资源的...
  • 《C++多核编程》 第六章 多线程

    千次阅读 2012-09-18 10:56:55
    第6章 多线程 在第5章中,我们查看了如何通过将程序分解为多个进程或多个线程而在C++程序中实现并发。我们讨论了进程,它是由操作系统创建的工作单元,解释了用于进程管理的POSIX API以及多个可用于创建进程的系统...
  • Java 多线程 并发 Java线程面试题

    千次阅读 2018-05-12 00:02:12
    程序员可以通过它进行多处理器编程,你可以使用多线程对运算密集型任务提速。比如,如果一个线程完成一个任务要100毫秒,那么用十个线程完成改任务只需10毫秒。Java在语言层面对多线程提供了卓越的支持,它也是一个...
  • 多线程threading 模块创建线程创建自己的线程类线程通信线程同步互斥方法线程@需要了解!!! 多线程 什么是线程? 线程也是一种多任务的编程方法,可以利用计算机多核资源完成程序的并发运行。 ...
  • GIL 与 Python 线程的纠葛 GIL 是什么东西?它对我们的 python 程序会产生什么样的影响?我们先来看一个问题。运行下面这段 python 程序,CPU 占用率是多少? # 请勿在工作中模仿,危险:) def dead_loop(): ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 29,086
精华内容 11,634
关键字:

多核多线程安全锁