Linux多线程编程

目录

一. 进程与线程

二. 为什么要使用线程

三. 线程开发概要

四. 与线程自身相关API

1.线程创建

2.线程退出

3. 线程等待

4. 线程ID获取

五. 与互斥锁相关API

 1. 创建及销毁互斥锁

2.加锁及解锁

 3. 例子:

六. 与条件变量相关API

 1. 创建及销毁条件变量

2. 等待

   例子:

   运行结果:

七.什么情况下会造成死锁


一. 进程与线程

* 进程担当着分配系统资源(CPU时间、内存等)的作用。在面向线程设计的系统中,进程本身不是基本运行单位,而是线程的容器,不创建线程的话至少有一个线程。一个进程中可以并发多个线程,每条线程并行执行不同的任务。

* 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

                  "进程——资源分配的最小单位,线程——程序执行的最小单位"

* 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响。

* 线程有自己的堆栈和局部变量,但线程没有单独的地址空间(同一进程内的线程共享进程的地址空间),一个线程死掉就等于整个进程死 掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。

二. 为什么要使用线程

(本部分摘自Linux多线程编程(不限Linux)

理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。

理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

理由之三是改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

三. 线程开发概要

多线程开发在 Linux 平台上已经有成熟的 pthread 库支持。其涉及的多线程开发的最基本概念主要包含三点:线程,互斥锁,条件。

线程操作又分线程的创建,退出,等待 3 种。

互斥锁则包括 4 种操作,分别是创建,销毁,加锁和解锁。

条件操作有 5 种操作:创建,销毁,触发,广播和等待。

四. 与线程自身相关API

1.线程创建

#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
// 返回:若成功返回0,否则返回错误编号

pthread_create成功返回时,由第一个参数tidp指向的内存单元被设置为新创建线程的线程ID。

第二个参数是线程的属性暂可以把它设置为NULL,以创建默认属性的线程。

第三个参数是一个函数指针,指向负责干活的函数

第四个参数是要给该线程传参的参数,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。

2.线程退出

#include <pthread.h>
int pthread_exit(void *rval_ptr);

  其参数是一个指向函数退出时传递返回值的指针。若无需传递线程退出时的返回值则为NULL

3. 线程等待

#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
// 返回:若成功返回0,否则返回错误编号

调用这个函数的线程将一直阻塞,直到线程退出

第一个参数是我们要等待的线程的ID值

第二个参数是一个二级指针,指向一级指针,一级指针指向线程退出时的返回值

4. 线程ID获取

#include <pthread.h>
pthread_t pthread_self(void);
// 返回:调用线程的ID

pthread_self() 函数是用于获取调用线程自身的线程 ID(Thread ID)的函数。这个函数不需要任何参数,它会返回调用线程的线程 ID

例子:

P1.c  线程的创建,等待及退出函数的应用

/**线程的创建,等待及退出*/
#include <stdio.h>
#include <pthread.h>

//新线程中负责干活的函数
void *func1(void *arg){
    //定义退出状态为10
    static int ret = 10;
    //打印新线程的ID号 
    printf("t1 :%ld thread is create\n",(unsigned long)pthread_self());
    printf("t1 :param is %d\n",*((int *)arg));
    //int pthread_exit(void *rval_ptr);
    pthread_exit((void *)&ret);
}

int main(){
    int ret;
    int param = 100;
    pthread_t t1;

//a.线程的创建
    //int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
    ret = pthread_create(&t1,NULL,func1,(void *)&param); //第二个参数是线程的属性暂可以把它设置为NULL,以创建默认属性的线程。
                                                         //第四个参数是给负责干活的函数传递的参数
    if(ret == 0){
        printf("create t1 success\n");
    }
    //当新线程成功创建后指针restrict tidp指向新线程的ID号
//b.打印新线程的ID
    printf("t1:%ld thread\n",(unsigned long)t1);
//c.打印main函数线程的ID
    printf("main :%ld thread is create\n",(unsigned long)pthread_self());

    //while(1);死循环防止主线程退出后新线程还没来得及运行或者调用等待函数
    int *pret = NULL;  //在这里我们定义为int类型是因为上面线程退出的状态是int类型
    //int pthread_join(pthread_t thread, void **rval_ptr);
//d.等待新线程的退出并获取其退出状态
    pthread_join(t1,(void **)&pret); //二级指针存储的是一级指针的地址。这里我们要传参的是一级指针的地址。因此,在这里我们要把一级指针的地址转化为(void **)类型,
//e.打印出新线程的退出状态
    printf("main:t1 quit:%d\n",*pret); 
    /**
     *  int *pret = NULL; 
        void *p = (void *)pret;
        pthread_join(t1,&p); 
        printf("main: t1 quit: %d\n", *(int *)p); 
    */
    return 0;
}

P2.c 线程共享内存空间验证及验证在该程序中三个线程互相竞争,无法控制先后顺序。

/**线程共享内存空间验证*/
/**在该程序中三个线程互相竞争,无法控制先后顺序*/
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int g_data = 0;

void *func1(void *arg){
    printf("t1: %ld thread is created\n", (unsigned long)pthread_self());
    printf("t1: param is %d\n", *((int *)arg));
    while(1){
        printf("t1: %d\n",g_data++);
        sleep(2);

        //zt1进程不一定会退出,因为func1不一定被t1进程抢到
        if(g_data == 3){
            pthread_exit(NULL);
        }
    }
}

void *func2(void *arg){
    printf("t2: %ld thread is created\n", (unsigned long)pthread_self());
    printf("t2: param is %d\n", *((int *)arg));
    while(1){
        printf("t2: %d\n",g_data++);
        sleep(2);
    }
}

int main(){
    int ret;
    int param = 100;
    pthread_t t1;
    pthread_t t2;
    ret = pthread_create(&t1, NULL, func1, (void *)&param);
    if(ret == 0){
        printf("create t1 success\n");
    }

    ret = pthread_create(&t2, NULL, func2, (void *)&param);
    if(ret == 0){
        printf("create t2 success\n");
    }

    printf("t1: %ld thread\n", (unsigned long)t1);
    printf("main: %ld thread is created\n", (unsigned long)pthread_self());

    while(1){
        printf("main: %d\n",g_data++);
        sleep(2);
    }

    pthread_join(t1, NULL); 
    pthread_join(t2, NULL);
    return 0;
}

五. 与互斥锁相关API

互斥量(mutex)从本质上来说是一把锁。在访问共享资源前进行加锁,在访问完成后释放锁。其用于保护共享资源,确保在任何时刻只有一个线程可以访问共享资源。并且保证了上锁后的线程会连续执行完毕。

 1. 创建及销毁互斥锁

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); //第二个参数是锁的类型,一般为NULL
int pthread_mutex_destroy(pthread_mutex_t* mutex);

        也可以直接使用宏去静态初始化互斥锁

        pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

2.加锁及解锁

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);

 3. 例子:

   以下案例基于全局变量g_data展开,通过利用互斥锁来避免多个线程竞争公共资源。

/**
   以下程序进行了创建锁,加锁,解锁,销毁锁操作
   用于保护共享资源,确保在任何时刻只有一个线程可以访问共享资源
   保证上锁后的一个线程的连续执行(但是可能会被main穿插进来)
   但不能控制线程的执行顺序
*/
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>

int g_data = 0;
//定义一把锁
pthread_mutex_t  mutex;

void *func1(void *arg){
    //1. 上锁
    //int pthread_mutex_lock(pthread_mutex_t* mutex);
    pthread_mutex_lock(&mutex);
    
    //2. 打印出t1线程的ID号,以及参数的值
    printf("t1: %ld thread is created\n", (unsigned long)pthread_self());
    printf("t1: param is %d\n", *((int *)arg));
    
    //3. 验证互斥锁能保护共享资源
    for(int i=0; i<5;i++){
        printf("t1 : %d\n",g_data++);
        sleep(1);
    }
    
    //4. 解锁
    //int pthread_mutex_unlock(pthread_mutex_t* mutex);
    pthread_mutex_unlock(&mutex);

}

void *func2(void *arg){
    //1. 上锁
    //int pthread_mutex_lock(pthread_mutex_t* mutex);
    pthread_mutex_lock(&mutex);
    
    //2. 打印出t2线程的ID号,以及参数的值
    printf("t2: %ld thread is created\n", (unsigned long)pthread_self());
    printf("t2: param is %d\n", *((int *)arg));
    
    //3. 验证互斥锁能保护共享资源
    for(int i=0; i<5;i++){
        printf("t2 : %d\n",g_data++);
        sleep(1);
    }
    
    //4. 解锁
    //int pthread_mutex_unlock(pthread_mutex_t* mutex);
    pthread_mutex_unlock(&mutex);
}

int main(){
    int ret; // 创建线程的返回值,用于判断线程是否成功创建
    int param = 100; //向线程中传递的参数值
    pthread_t t1; 
    pthread_t t2; 

    //A. 在创建线程之前就初始化这把锁,第二个参数是锁的属性
    //也可以使用宏初始化互斥锁
    //pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    pthread_mutex_init(&mutex,NULL);

    
    //B. 创建线程
    ret = pthread_create(&t1, NULL, func1, (void *)&param);
    if(ret == 0){
        printf("main: create t1 success\n");
    }
    ret = pthread_create(&t2, NULL, func2, (void *)&param);
    if(ret == 0){
        printf("main: create t2 success\n");
    }

    //C. 打印主线程的ID
    printf("main: %ld thread is created\n", (unsigned long)pthread_self());

    //D. 主线程等待新线程的退出
    pthread_join(t1, NULL); 
    pthread_join(t2, NULL);

    //E. 销毁这把锁
    pthread_mutex_destroy(&mutex);
   
    return 0;
}

六. 与条件变量相关API

 1. 创建及销毁条件变量

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t cond);

        也可以直接使用宏去静态初始化条件变量

        pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 

2. 等待

#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

   例子:

        以下案例利用条件变量,弥补了只使用互斥锁难以使得我们想让t1线程竞争到g_data==3的情况。

/**
 * 1.初始时 g_data = 0,线程 t1 和线程 t2 都在自己的循环中等待条件满足。
   2.如果 g_data != 3,则 t1 等待条件变量的信号时无法获得互斥锁 mutex,因为 pthread_cond_wait 在等待条件时会自动释放互斥锁。这时 t2 可以获得互斥锁并操作 g_data。
   3.当 t2 操作使得 g_data == 3 时,会发送信号唤醒 t1。
   4.t1 被唤醒后会获得互斥锁 mutex,执行相关操作后将 g_data 设置为 0,并在下一次循环开始时再次等待条件满足。在等待条件期间,mutex 会被释放。
   5.由于 pthread_cond_wait 在等待时会释放互斥锁,因此 t2 可以在 t1 释放锁后再次获取锁,对 g_data 进行操作。
*/
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>

int g_data = 0;
pthread_mutex_t  mutex; //互斥锁
pthread_cond_t cond; //条件

//t1上锁和解锁直接由wait函数实现
void *func1(void *arg){
    printf("t1: %ld thread is created\n", (unsigned long)pthread_self());
    printf("t1: param is %d\n", *((int *)arg));

    int cnt = 0;
    
    while(1){
        //等待
        pthread_cond_wait(&cond,&mutex); //在等待过程中会释放互斥锁 mutex,
                                         //以便其他线程可以获取互斥锁并修改条件变量所关联的数据。
        printf("t1 run =====================================\n");
        printf("t1 : %d\n",g_data++);
        g_data = 0;
        cnt++;
        if(cnt == 3){
            exit(0);
        }
        sleep(1);
    }

}

void *func2(void *arg){
    printf("t2: %ld thread is created\n", (unsigned long)pthread_self());
    printf("t2: param is %d\n", *((int *)arg));

    while(1){
        printf("t2 : %d\n",g_data);
        pthread_mutex_lock(&mutex);
        g_data++;

        if(g_data == 3){ //唤醒线程t1
            pthread_cond_signal(&cond);
        }
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
}

int main(){
    int ret;
    int param = 100;
    pthread_t t1;
    pthread_t t2;

    //b.在创建线程之前就初始化这把锁
    pthread_mutex_init(&mutex,NULL); //第二个参数是锁的属性
    //创建并初始化条件变量
    pthread_cond_init(&cond,NULL);


    ret = pthread_create(&t1, NULL, func1, (void *)&param);
    if(ret == 0){
        printf("main: create t1 success\n");
    }

    ret = pthread_create(&t2, NULL, func2, (void *)&param);
    if(ret == 0){
        printf("main: create t2 success\n");
    }
    printf("main: %ld thread is created\n", (unsigned long)pthread_self());
    pthread_join(t1, NULL); 
    pthread_join(t2, NULL);

    //销毁这把锁
    pthread_mutex_destroy(&mutex);

   pthread_cond_destroy(&cond);
    return 0;
}

   运行结果:

当要求我们测试多遍时,可编写下面脚本

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

int main(int argc,char *argv[]){
    int time = atoi(argv[1]);
    for(int i = 0;i < time ;i++){
        system("./P6.out");
    }
    return 0;
}

运行结果:

执行俩遍并将运行结果存放于test.ret.txt文本文档中

七.什么情况下会造成死锁

假设我们现在有俩把锁。线程1持有锁1并尝试获取锁2,而线程2持有锁2并尝试获取锁1,这会导致双方相互等待对方释放锁而无法继续执行,造成了死锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@尘音

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值