pthread共享内存编程学习(1) -- 基本语法学习

pthread共享内存编程学习(1) – 基本语法学习


本次学习以课本《并行程序设计导论》第四章为主,并且学习其他dl们的博客,做一些学习记录。

1.hello pthread(基础小例子)

例子

注释里有对程序的解释,和我对不懂的地方的注解

编译语句:

gcc -g -Wall -o test test.c
# -g debug模式
# -Wall 开启所有警告
#-o 指定输出文件名,该文件为可执行文件,不加-o会默认生成a.out
#-c (compile)只编译生成中间同名目标文件,不链接 
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

//全局变量 被所有线程所共享
 //在函数中声明的局部变量由执行该函数的线程所私有 
int thread_count;

//void的字面意思是“无类型”,void *则为“无类型指针”,void *可以指向任何类型的数据。 
void* Hello(void* rank);

int main(int argc, char** argv) {
    
    
	long thread;
	pthread_t* thread_handles;
	
	//strtol 将字符串转换为long int;由stdlib.h声明 
	//参数:指向被转换的字符串的指针,指向字符串中第一个无效字符的指针,进制 
	thread_count = strtol(argv[1], NULL, 10); 
printf("%daaa\n",thread_count);
	
	//为每个线程的pthread_t对象分配内存
	//pthread_t数据结构用来存储线程的专有信息
	 //pthread_t是不透明对象,对象中存储的数据都是系统绑定的,用户级代码无法直接访问里面的数据
/ 
	thread_handles = malloc(thread_count*sizeof(pthread_t)); 
	
	for(thread = 0; thread < thread_count; thread++) {
    
    
		//pthread_create 生成线程
		 // 参数:指向对应的pthread_t对象, ,线程要运行的函数,指针,指向要传入的参数
		 // 返回值表示多线程调用是否有错误 
		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;
} 

void* Hello(void* rank) {
    
    
	long my_rank = (long) rank;
	printf("Hello from thread %ld of %d\n",my_rank, thread_count);
}

pthtread基本知识

  • 在pthread程序中,全局变量被所有线程所共享,而在函数中声明的局部变量则通常由执行该函数的线程所私有。

  • pthread_t:类型名称

    • 当定义好一个线程后,需要显式的为pthread_t对象声明内存

      pthread_t thread; thread = malloc(sizeof(pthread_t));

    • 不透明的对象,对象中存储的数据都是系统绑定的,用户级代码无法直接访问到里面的数据

  • pthread_create():生成线程

    • 参数列表

      • pthread_t* //out 指向pthread_t对象的指针

      • 第二个参数不需要,直接填入NULL

      • void* //in 线程要执行的函数

      • void* //in 函数的参数(可以传入结构体)

        在例子中,通过将int变量强转成void*,在函数中再将void*强转为int

  • pthread_join():停止进程

    • 参数列表
      • pthread_t // in 等待的pthread对象
      • 返回值,填入NULL即可
    • 以阻塞的方式等待thread指定的线程结束,然后返回。
    • 如果没有join函数,主线程不会等待子线程完成返回,会直接返回。
  • free:释放线程

小问题记录

在刚开始写代码的时候,运行结果会报一个段错误的提醒·

Segmentation fault(core dumped) 段错误

在这里插入图片描述

排查后发现是自己手残写错了一个地方:malloc的时候,将sizeof(pthread_t)写成sizeof(thread_count),所以会产生分配的空间小于应分配的空间

这个问题本身没啥,但是有个问题比较奇怪:没有被分配空间的线程可以执行对应的函数(即ptread_create函数没问题),但是在pthread_join函数的时候会报错:循环到没分配内存的线程时报错。

考虑什么情况下会报段错误:段错误应该就是访问了不可访问的内存,这个内存区要么是不存在的,要么是受到系统保护的,还有可能是缺少文件或者文件损坏。

因为没有阅读phread_join的源代码(比较懒,而且感觉应该看不懂),所以这里大胆的推断一下(假设有8个线程,但是只分配了前4个的空间):

  • 没有分配的后四个pthread_t对象,eg: thread_handles[6],他指向的内存空间并没有被实际被分配出来,也就是指向了一个不属于他的内存;但是雀氏存在一个pthtread_t对象。
  • 执行pthread_create方法的时候,不需要涉及到该线程对象所指向的内存空间,所以可以正常执行(说实话挺离谱的)
  • 执行pthread_join方法的时候,要释放线程之前被分配的内存空间;问题来了,这些线程之前并没有被分配空间,也就是函数认为这块空间是属于线程的,想要释放它,但实际上并不属于线程,所以造成了 访问了不可访问的内存(应该是被保护了8),所以报错。

所以最后还有一个小问题没有确定:为什么没有被malloc的pthread_t也能够执行pthread_create函数来执行这个线程。(以后解答 or 有大佬看到这个问题解答一下)

2.互斥量 mutex

例子

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

pthread_mutex_t mutex;//互斥量

//全局变量 被所有线程所共享
 //在函数中声明的局部变量由执行该函数的线程所私有 
int thread_count;

void* Hello(void* rank);

int main(int argc, char** argv) {
    
    
	long thread;
	pthread_t* thread_handles;
	
	pthread_mutex_init(&mutex, NULL);//初始化互斥量
	
	thread_count = strtol(argv[1], NULL, 10); 
	thread_handles = 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);
	} 
	//销毁互斥量
	pthread_mutex_destroy(&mutex);
	free(thread_handles);

	
	return 0;
} 

void* Hello(void* rank) {
    
    
	long my_rank = (long) rank;
	//printf("Hello from thread %ld of %d\n",my_rank, thread_count);
    //申请互斥锁
	pthread_mutex_lock(&mutex);
	printf("mutex from thread %ld of %d\n",my_rank, thread_count);
    //释放互斥锁
	pthread_mutex_unlock(&mutex);
}

互斥量基本知识

  • pthread_mutex_t:类型名称

  • pthread_mutex_init():在使用互斥量之前,需要对其进行初始化

    • 参数:
      • pthread_mutex_t*
      • null
  • pthread_mutex_destory():当Pthread程序使用完互斥量后

    • 参数:
      • pthread_mutex_t*
  • pthread_mutex_lock():获得临界区的访问权

    • 参数:pthread_mutex_t*
  • pthread_mutex_unlock():退出临界区

    • 参数:pthread_mutex_t*

互斥量 vs 忙等待

忙等待:通俗的说就是一个while死循环。

define flag = true

while(flag != true) ;
flage = false;
...
flag = true;

直观的看互斥量和忙等待,观感上二者相差不大。

区别:互斥量相比于忙等待对CPU有更好的利用率

互斥量:当互斥量已经被lock以后,其他线程想要进入临界区,调用了lock函数后,会阻塞并等待,直到第一个线程离开临界区。

注意:Pthread无法保证线程按照调用pthread_mutex_t的顺序获得进入临界区的锁,但是在设定中,只用有限个线程在尝试获得锁的所有权,最终每一个进程都会获得锁。

线程状态:https://blog.csdn.net/xingjing1226/article/details/81977129

在这里插入图片描述

阻塞并等待:线程会释放CPU的资源,进入等待队列,直到等待资源的释放。

总结:比较使用忙等待和互斥量的程序性能,当线程个数少于CPU核数时,发现二者的执行时间并没有很大的差别;当线程数多余CPU核数时,则使用互斥量的性能就会优于忙等待。

re:使用互斥量的程序,当某个线程占据了一个核,他要进入临界区的时候,发现没有权限,那么会被阻塞并等待,并且释放占据的CPU资源,这些CPU资源会被其他线程所使用;使用忙等待的线程,发现没有权限后,会一直进行while死循环,在这期间一直占用CPU,该CPU资源相当于处于闲置状态,所以利用率低。

参考博客

线程5中状态详解 https://blog.csdn.net/xingjing1226/article/details/81977129

段错误(核心已转储)(core dumped)问题的分析方法 https://blog.csdn.net/weixin_40877924/article/details/108762118

主要内容都是书里的

Guess you like

Origin blog.csdn.net/qq_38170211/article/details/121501850