天天看点

Linux多线程编程初探Linux多线程编程初探

目录

  • Linux多线程编程初探
    • 任务内容:读者-写者问题多线程实现
    • 用到的函数
      • pthread_create()
      • pthread_exit()
      • pthread_join()
      • pthread_attr_init()
      • pthread_mutex_init() 互斥锁系列
      • sem_init() 信号量系列
    • 任务内容实现
      • 写线程和读线程均处理单个数据
      • 由用户向文件写入字符串,读线程读取文件
    • 利用qemu用户模式进行gdb-gdbserver调试

Linux多线程编程初探

对学习任务进行一下记录。主要包括各个thread库中各个函数的用法,以及任务中编程实现的步骤。

任务内容:读者-写者问题多线程实现

任务内容:

一个数据集(如数据、文件等)被N个线程读写;

 一些线程只要求读数据集内容,称为读者 (Reader);

 一些线程要求修改数据集内容,称为写者 (Writer);

 多个读者可以同时读数据集,不需要互斥操作;

 一个写者不能和其他写者或读者同时访问数据集,换句话说,写

者和其他写者或读者之间必须互斥操作!

 实现读者优先:如果有读者(readercount>0),写者需要等待!

用于更新不频繁或更新影响不显著、读者较多的场合;

 实现写者优先:如果有写者(writercount>0) ,读者需要等待!用

于更新频繁或更新影响显著的场合;

 实现读写公平:按照读写到达顺序操作,用于读写较平衡场合;

 基于不同策略下的读写时序分析输出结果,检查是否达到目的;

 本地调试后,加载到树莓派或仿真arm的qemu上执行。

用到的函数

pthread_create()

函数原型:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)

(void *), void *arg);

函数描述:

线程的创建,线程运行完毕通过调用pthread_exit()退出

输入参数:

thread:指向线程标示符pthread_t数据类型的指针,据此来引用线程

attr:设置线程的属性。如果不需要设置,设置NULL,一般不需要其它摄制

*start_routine:线程运行函数的起始地址。传入和返回参数均为void类型的指针

arg:运行函数的参数。void类型意在传递任意类型的参数

返回值 成功返回0,失败返回错误码

Linux多线程编程初探Linux多线程编程初探

pthread_exit()

函数原型: void pthread_exit(void* retval);

函数描述: 终止调用它的线程,并且返回一个指向某个对象的指针

输入参数: 无

返回值: 返回值存在void *retval中

Linux多线程编程初探Linux多线程编程初探

使用范例:

Linux多线程编程初探Linux多线程编程初探

pthread_join()

线程阻塞函数,调用者将处于等待状态一直到被等待的线程结束:

 pthread_join()一般由主线程来调用,等待各个创建的子线程退出

 pthread_exit()一般由子线程调用,用来结束当前线程

 该函数类似于进程调用的wait()函数

Linux多线程编程初探Linux多线程编程初探

使用范例:

Linux多线程编程初探Linux多线程编程初探

pthread_attr_init()

 线程属性初始化,为调用属性设置函数做准备

Linux多线程编程初探Linux多线程编程初探

pthread_mutex_init() 互斥锁系列

初始化函数原型:

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * attr);

第一个参数是mutex量指针,第二个参数是属性,输入NULL为默认(快速互斥锁)。

互斥锁相关函数:

int pthread_mutex_lock(pthread_mutex_t *mutex);

互斥锁上锁,假如互斥锁已经上锁,则一直等待到被解锁。

int pthread_mutex_trylock(pthread_mutex_t *mutex);

互斥锁上锁,假如互斥锁已经上锁,无等待,但返回错误

int pthread_mutex_unlock(pthread_mutex_t *mutex);

互斥锁解锁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

删除互斥锁,前提是互斥锁当前没有被锁住

sem_init() 信号量系列

初始化函数原型:

int sem_init(sem_t *sem, int pshared, unsigned int value);

描述:创建一个信号量并初始化它的值

返回:成功返回 0;错误返回 -1,设置 errno

第一个参数是sem量指针;

第二个参数pshared:

 pshared=0 用于同一进程中线程间的同步,目前只选0

 pshared>0 用于进程间的同步

第三个参数value:信号量的初值

其他信号量系列函数:

int sem_getvalue(sem_t *sem, int *sval);

描述:取回信号量sem的当前值,保存到sval中

返回:成功返回 0;错误返回 -1,设置 errno

int sem_wait(sem_t *sem);

描述:P操作,等待获取一个信号量,这是一个阻塞性的函

数,测试所指定信号量的值,若sem>0,它减1并立即返回

;若sem=0,则睡眠直到sem>0,然后减1返回。

返回:成功返回 0;错误返回 -1,设置 errno

int sem_trywait(sem_t *sem);

描述:P操作,获取一个信号量,这是一个非阻塞性的函数

,测试所指定信号量的值,若sem>0,它减1并立即返回;

若sem=0,不是睡眠等待,而是立即返回一个错误标志

返回:成功返回 0;错误返回 -1,并设置 errno=EAGAIN

int sem_post(sem_t *sem);

描述:V操作,释放一个信号量,把指定的信号量sem的值

加1,唤醒正在等待该信号量的线程,如果有多个线程等待

,由调度机制决定唤醒哪一个。

返回:成功返回 0;错误返回 -1,设置 errno

int sem_destroy(sem_t *sem);

描述:删除一个信号量,前提是该信号量没有被占用

返回:成功返回 0;错误返回 -1,设置 errno

ps:互斥锁应为信号量的特殊状况,当信号量sem初始值为1时,wait即为lock,post即为unlock。

任务内容实现

对于读写文件,起初采用了读写单个数据的方式,设int sharedata,给个随机值;但是后来发现不太好观察进程的使用情况,又采用了读写txt文件的方式,由用户输入值。(其实都差不多)

写线程和读线程均处理单个数据

读动作:

//读动作
void READ(){
    printf("读到数据 %d\n\n",SharedData);
}
           

写动作:

//写动作
void WRITE(){
    int temp=rand();
    printf("写入数据 %d\n\n",temp);
    SharedData=temp;
}
           

以读者优先模式为例:

//读者优先模式,读者函数
void *readmd_reader(){
    //while(1){
    sem_wait(&sem_readcnt);  //sem_readcnt初始设为1,虽然每个时刻可以有多个读动作进行,但是每个时刻只有一个线程进行count计数。
    readcnt++;
    if(readcnt==1)  {     //在第一次读进程送来时,将写进程锁住(阻塞住),如果后续有新的读进程输入,不会造成死锁。    
	pthread_mutex_lock(&write_mutex);
    }
    sem_post(&sem_readcnt);  //以上为读进程输入的计数程序
    printf("读线程id %d 进入程序\n",pthread_self());
    READ();
    sem_wait(&sem_readcnt);
    readcnt--;
    printf("读线程id %d 退出程序\n\n",pthread_self());
    if(readcnt==0)       //所有读进程结束后,写进程才取消阻塞。
	pthread_mutex_unlock(&write_mutex);
    sem_post(&sem_readcnt);
    sleep(slp_size);
    //}
    pthread_exit((void*)0);
}

//读者优先模式,写者函数
void *readmd_writer(){
    //写者之间互斥,不用考虑sem_writer.
    writecnt++;
    pthread_mutex_lock(&write_mutex);
    printf("写线程id %d 进入程序\n",pthread_self());
    WRITE();
    writecnt--;
    pthread_mutex_unlock(&write_mutex);
    printf("写线程id %d 退出程序\n\n",pthread_self());
    sleep(slp_size);
    pthread_exit((void*)0);
}
           

一开始读者函数用的判定条件是if(readcnt>=1) ,但是会造成死锁,因为每次读进程进来时都会因为lock阻塞住,所以进程计数器不会增加,需要改成if(readcnt==1)。

运行结果:

Linux多线程编程初探Linux多线程编程初探

由于读者优先,所以最初读到的数据是零。

由用户向文件写入字符串,读线程读取文件

为了更好观察进程的使用情况,又采用了读写txt文件的方式,由用户输入值:

//读文件函数:
void READ(){
    printf("读取文件内容如下:\n");
    int fp = open("test.txt", O_RDWR);
    if(fp<0){
        perror("open failed!\n");
	exit(1);
    }
    int res = read(fp, readstr, STR_SIZE);
    printf("%s\n\n",readstr);
    close(fp);
}

//写函数:
void WRITE(){
    int res ;
    char p1[STR_SIZE];
    printf("请输入字符串:\n");
    scanf("%s",p1);
    int fp = open("test.txt", O_RDWR|O_CREAT|O_TRUNC);
    if(fp<0){
        perror("open failed!\n");
	exit(1);
    }
    int len = strlen(p1);
    res = write(fp, p1, len);
    if (res != len){
        printf("Error writing to the file.\n");
        exit(1);
    }
    printf("Wrote %d bytes to the file.\n", res);
    close(fp);
}
           

运行结果:

Linux多线程编程初探Linux多线程编程初探
Linux多线程编程初探Linux多线程编程初探

可以看到,多个读程序的打印信息发生了交叉,可以证明多个读进程同时读文件。

但是由于新手水平有限,对于如何纠正这个交叉,我将读进程中的读动作上了把锁,锁内有打印信息的指令和读动作:(以写者模式的读进程为例)

//写者优先模式,读者函数
void *writemd_reader(){
    
    //sem_wait(&sem_readcnt);           
    readcnt++;
    //sem_post(&sem_readcnt); 
    pthread_mutex_lock(&read_mutex);                           //此处加了把锁!!!
    printf("读线程id %d 进入程序\n当前读者线程数为%d,当前等待写者数为%d\n\n",pthread_self(),readcnt,writecnt);
    
    READ();
    
    printf("读线程id %d 退出程序\n\n",pthread_self());
    pthread_mutex_unlock(&read_mutex);
    //sem_wait(&sem_readcnt);
    readcnt--;
    
    //sem_post(&sem_readcnt);
    
    sleep(slp_size);
    pthread_exit((void*)0);
}
           

这样写者模式运行如下,没发生交叉情况:

Linux多线程编程初探Linux多线程编程初探

但是个人认为这样就不算多个进程同时读了。如果不改变打印信息的指令位置,就不可实现读动作的同时进行和不交叉打印信息的同时实现,对此还有待继续探究,所以暂时只有写者模式进行了这样的改进。

完整代码如下:

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <semaphore.h>
#include <unistd.h>
#define STR_SIZE 100

#define slp_size    1              //进程进行的频率

char readstr[STR_SIZE];

pthread_t wid[100],rid[100];

pthread_mutex_t  read_mutex;    //读互斥锁
pthread_mutex_t  write_mutex;   //写互斥锁
sem_t sem_readcnt;               //读计数器的控制信号量
sem_t sem_writecnt;              //写计数器的控制信号量

int readcnt = 0;
int writecnt = 0;


//读动作
void READ(){
    printf("读取文件内容如下:\n");
    int fp = open("test.txt", O_RDWR);
    if(fp<0){
        perror("open failed!\n");
	exit(1);
    }
    int res = read(fp, readstr, STR_SIZE);
    printf("%s\n\n",readstr);
    close(fp);
}

//写动作
void WRITE(){
    int res ;
    char p1[STR_SIZE];
    printf("请输入字符串:\n");
    scanf("%s",p1);

    int fp = open("test.txt", O_RDWR|O_CREAT|O_TRUNC);

    if(fp<0){
        perror("open failed!\n");
	exit(1);
    }
    int len = strlen(p1);
    res = write(fp, p1, len);
    if (res != len){
        printf("Error writing to the file.\n");
        exit(1);
    }
    printf("Wrote %d bytes to the file.\n", res);

    close(fp);
}


//读者优先模式,读者函数
void *readmd_reader(){
    //while(1){
    sem_wait(&sem_readcnt);  //sem_readcnt初始设为1,虽然每个时刻可以有多个读动作进行,但是每个时刻只有一个线程进行count计数。
    readcnt++;
    
    if(readcnt==1)  {     //在第一次读进程送来时,将写进程锁住(阻塞住),如果后续有新的读进程输入,不会造成死锁。
        
	pthread_mutex_lock(&write_mutex);

    }
    sem_post(&sem_readcnt);  //以上为读进程输入的计数程序
    printf("读线程id %d 进入程序\n",pthread_self());
    READ();

    sem_wait(&sem_readcnt);
    readcnt--;
    printf("读线程id %d 退出程序\n\n",pthread_self());
    if(readcnt==0)       //所有读进程结束后,写进程才取消阻塞。
	pthread_mutex_unlock(&write_mutex);
    sem_post(&sem_readcnt);
    sleep(slp_size);
    //}
    pthread_exit((void*)0);
}



//读者优先模式,写者函数
void *readmd_writer(){
    //写者之间互斥,不用考虑sem_writer
    writecnt++;
    pthread_mutex_lock(&write_mutex);
    printf("写线程id %d 进入程序\n",pthread_self());
    WRITE();
    writecnt--; 
    pthread_mutex_unlock(&write_mutex);
    printf("写线程id %d 退出程序\n\n",pthread_self());
    sleep(slp_size);
    pthread_exit((void*)0);
}


//写者优先模式,读者函数
void *writemd_reader(){       
    readcnt++;
    pthread_mutex_lock(&read_mutex);
    printf("读线程id %d 进入程序\n当前读者线程数为%d,当前等待写者数为%d\n\n",pthread_self(),readcnt,writecnt);
    READ();
    printf("读线程id %d 退出程序\n\n",pthread_self());
    pthread_mutex_unlock(&read_mutex);
    readcnt--;
    sleep(slp_size);
    pthread_exit((void*)0);
}


//写者优先模式,写者函数
void *writemd_writer(){
    //sem_wait(&sem_readcnt);
    writecnt++;
    if(writecnt==1){
        //sem_wait(&sem_readcnt);
	pthread_mutex_lock(&read_mutex);
    }
    pthread_mutex_lock(&write_mutex);
    printf("写线程id %d 进入程序\n当前等待读者数为%d,当前写者线程数为%d\n\n",pthread_self(),readcnt,writecnt);
    //pthread_mutex_lock(&write_mutex);
    WRITE();
    //pthread_mutex_unlock(&write_mutex);
    writecnt--;
    pthread_mutex_unlock(&write_mutex);
    if(writecnt==0){
	//sem_post(&sem_readcnt);
	pthread_mutex_unlock(&read_mutex);
    }
    //sem_post(&sem_readcnt);
    printf("写线程id %d 退出程序\n\n",pthread_self());
    sleep(slp_size);
    pthread_exit((void*)0);
}

//读写均衡模式,读者函数
void *rwmd_reader(){
    sem_wait(&sem_readcnt);
    readcnt++;
    sem_post(&sem_readcnt); 
    printf("读线程id %d 进入程序\n当前读者线程数为%d,当前等待写者数为%d\n\n",pthread_self(),readcnt,writecnt);
    READ();
    printf("读线程id %d 退出程序\n\n",pthread_self());
    sem_wait(&sem_readcnt);
    readcnt--;
    sem_post(&sem_readcnt);
    sleep(slp_size);
    pthread_exit((void*)0);
}


//读写均衡模式,写者函数
void *rwmd_writer(){
    sem_wait(&sem_readcnt);
    writecnt++;
    printf("写线程id %d 进入程序\n当前等待读者数为%d,当前写者线程数为%d\n\n",pthread_self(),readcnt,writecnt);
    pthread_mutex_lock(&write_mutex);
    WRITE();
    pthread_mutex_unlock(&write_mutex);
    writecnt--;
    printf("写线程id %d 退出程序\n\n",pthread_self());
    sem_post(&sem_readcnt);
    sleep(slp_size);
    pthread_exit((void*)0);
}


int main(){
    int mode,i,readnumber,writenumber;
    pthread_mutex_init(&read_mutex,NULL);
    pthread_mutex_init(&write_mutex,NULL);
    sem_init(&sem_readcnt,0,1);
    sem_init(&sem_writecnt,0,1);
    printf("请输入读者和写者的数量。\n");
    printf("读者:");
    scanf("%d",&readnumber);
    printf("写者:");
    scanf("%d",&writenumber);
    printf("请输入模式,0代表读者优先,1代表写者优先,2代表读写均衡。\n");
    scanf("%d",&mode);
    if(mode==0){                               //读者优先
	for(i=0;i<readnumber;i++){
	    pthread_create(&rid[i],NULL,readmd_reader,NULL);
	}
	for(i=0;i<writenumber;i++){
            pthread_create(&wid[i],NULL,readmd_writer,NULL);
	}
	sleep(20);
    }
    else if(mode==1){                          //写者优先
	    for(i=0;i<writenumber;i++)
            pthread_create(&wid[i],NULL,writemd_writer,NULL);
	    for(i=0;i<readnumber;i++)
	    	pthread_create(&rid[i],NULL,writemd_reader,NULL);
		sleep(20);
    }
    else if(mode==2){                          //读写均衡
        for(i=0;i<writenumber;i++)
            pthread_create(&wid[i],NULL,rwmd_writer,NULL);
	    for(i=0;i<readnumber;i++)
	        pthread_create(&rid[i],NULL,rwmd_reader,NULL);
	    sleep(20);
    }

    return 0;
}
           

利用qemu用户模式进行gdb-gdbserver调试

$ sudo apt-get install qemu-user-static (安装用户模式仿真工具)

$ sudo apt-get install qemu (安装整体qemu仿真工具)

$ sudo apt-get install gcc-arm-linux-gnueabihf (安装交叉编译器工具)

$ sudo apt-get install gdb-multiarch (用于host端(电脑端)多CPU架构调试)

$ arm-linux-gnueabihf-gcc -g 读写.c -o 读写 -static -lpthread(交叉编译)

终端一输入如下:

Linux多线程编程初探Linux多线程编程初探

终端二输入如下然后进入gdb:

Linux多线程编程初探Linux多线程编程初探

输入c继续运行:

Linux多线程编程初探Linux多线程编程初探

此时终端一继续运行:

Linux多线程编程初探Linux多线程编程初探
Linux多线程编程初探Linux多线程编程初探

调试结果和之前一样。(废话)

继续阅读