线程间通信

目录

一、线程理论基础

1、什么是线程

2、为什么有了进程,还要引入线程呢? (使用多线程到底有哪些好处?)

3、线程的缺点

二、多线程间通信

1、多线程通信的注意事项

2、创建线程的步骤

3、终止线程的方式

4、线程 API 使用 

(1)pthread_create() 函数 (创建线程)

(2)pthread_exit()函数  (结束线程)

(3)pthread_join()函数 (等待线程结束)

三、线程同步

1、互斥量

(1)互斥量注意事项

(2)互斥锁创建步骤

(3)互斥锁 API 使用

        (a)pthread_mutex_t 标识符 创建互斥锁

        (b)pthread_mutex_init() 函数(互斥锁初始化)

        (c)pthread_mutex_lock() 函数(上锁)

        (d)pthread_mutex_unlock()函数(解锁)

        (e)pthread_mutex_destory()函数(销毁锁)

2、条件变量

(1)互斥量注意事项

(2)互斥锁创建步骤

(3)互斥锁 API 使用

        (a)pthread_cond_t 标识符(创建条件变量)

        (b)pthread_cond_init()函数(函数条件变量初始化)

        (c)pthread_cond_wait()函数(条件变量等待)

                pthread_cond_signal() 函数 (发信号)

        (d)pthread_cond_destroy()函数(条件变量撤销)

3、互斥锁与条件变量连用关系


一、线程理论基础

1、什么是线程

线程是进程的实体,是CPU调度和分派的基本单位,它是比进程更小的,能独立运行的基本单位。线程基本不拥有系统资源,只是拥有一点在运行中必不可少的资源(如:程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

2、为什么有了进程,还要引入线程呢? (使用多线程到底有哪些好处?)

(1)进程实现多任务的缺点:进程间切换计算机资源开销很大,切换效率非常低;进程间数据共享的开销也很大。

(2)从资源上来讲:和进程相比,它是一种非常“节俭”的多任务操作方式.在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。

(3)从切换效率上来讲:运行于一个进程中的多个线程,它们之间使用相同的地址空间,而且线程间彼此切换所需的时间也远远小于进程间切换所需要的时间.据统计,一个进程的开销大约是一个线程开销的30倍左右。

(4) 从通信机制上来讲:线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过进程间通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便

(5)除了以上所说的优点外,多线程程序作为一种多任务、并发的工作方式,有如下优点:  

使多CPU系统更加有效.操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。

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

3、线程的缺点

(1)等候使用共享资源时造成程序的运行速度变慢。这些共享资源主要是独占性的资源 ,如打印机等。

(2)对线程进行管理要求额外的 CPU开销。线程的使用会给系统带来上下文切换的额外负担。当这种负担超过一定程度时,多线程的特点主要表现在其缺点上,比如用独立的线程来更新数组内每个元素。

(3)线程的死锁。即较长时间的等待或资源竞争以及死锁等多线程症状。

(4)对公有变量的同时读或写。当多个线程需要对公有变量进行写操作时,后一个线程往往会修改掉前一个线程存放的数据,从而使前一个线程的参数被修改;另外 ,当公用变量的读写操作是非原子性时,在不同的机器上,中断时间的不确定性,会导致数据在一个线程内的操作产生错误,从而产生莫名其妙的错误,而这种错误是程序员无法预知的。

二、多线程间通信

1、多线程通信的注意事项

添加:

#include <pthread.h>

gcc  [   ]  -lpthread

2、创建线程的步骤

(1)创建线程,使用 pthread_create() 函数

(2)结束线程,使用 pthread_exit() 函数

(3)等待线程结束,使用pthread_join() 函数

添加:pthread_self()  取线程  id .

3、终止线程的方式

如果进程中任何一个线程中调用exit或_exit,那么整个进程都会终止。

线程的正常退出方式有:

(1) 线程从启动例程中返回(线程寿终正寝)

(2) 线程可以被另一个进程终止(线程被他杀)

(3) 线程自己调用pthread_exit函数(线程自杀)

4、线程 API 使用 

(1)pthread_create() 函数 (创建线程)

#include <pthread.h>

int pthread_create(pthread_t * tidp, const pthread_attr_t*attr, void*(*start_rtn)(void), void*arg)

返回值:成功:0;失败:-1

tidp:线程 id  (输出参数)

attr: 线程属性 (通常为空  NULL)

start_rtn:线程函数(该形参填写:(void *)函数名 )

线程函数:(线程函数形参:(void *)或 void; return 值 传给 pthread_join函数的第二个形参 )

arg:start_rtn的参数(线程函数的形参,自定义值)

(2)pthread_exit()函数  (结束线程)

#include <pthread.h>

void pthread_exit(void *rval_ptr)

功能:终止调用线程

Rval_ptr:线程退出返回值的指针(该形参会传给 pthread_join函数的第二个形参)

(3)pthread_join()函数 (等待线程结束)

#include <pthread.h>      

int pthread_join(pthread_t tid, void **rval_ptr)

返回值:成功:0;失败:-1

功能:阻塞调用线程,直到指定的线程终止。

Tid :等待退出的线程id

Rval_ptr:线程退出的返回值的指针(来源:线程函数的return 值;pthread_exit()函数的形参)

举例一:线程创建及相关用法

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

char message[] = "hello world!";

void *function(void *arg)
{
    printf("pthread : message is %s\n", (char *)arg);

    strcpy(message, "hello home!");

    pthread_exit("pthread exit!");

    //return arg;
}

int main(int argc, char *argv[])
{
    int ret;
    pthread_t pid;
    void *result = NULL;


    if((ret = pthread_create(&pid, NULL, (void *)function, message)) !=0)
    {
        perror("pthread create error!");
        exit(-1);
    }

    printf("wait pthread finsih .......!\n");

    if((ret = pthread_join(pid, &result)) != 0)
    {
        perror("pthread join error!");
        exit(-1);
    } 

    printf("result of main is %s\n",(char*)result);

    printf("message became is %s\n", message);

    return 0;
}
bxp@ubuntu:~/2022/0501$ ./pthread_creat 
wait pthread finsih .......!
pthread : message is hello world!
result of main is pthread exit!
message became is hello home!
bxp@ubuntu:~/2022/0501$ ./pthread_creat 
wait pthread finsih .......!
pthread : message is hello world!
result of main is hello home!
message became is hello home!

举例二:不同的线程执行不同的任务;(使用两个线程)

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


char result_val1[6] = "hhhhh";
char result_val2[6] = "hhhhh";

void *pthread_func1(void *arg)
{
    printf("func1 : arg is %s\n", (char *)arg);

    strcpy(result_val1, "hello");

    pthread_exit((void *)result_val1);
    
}

void *pthread_func2(void *arg)
{
    int i = 0;

    printf("func2 : arg is %s\n", (char *)arg);


    while ((*(char *)arg) != '\0')
    {
        result_val2[i++] = (*(char *)arg) - 32;

        (char *)arg++;
    }

    result_val2[i] = '\0';

    
    return (void *)result_val2;
}

int main(int argc, char *argv[])
{
    pthread_t pid1;
    pthread_t pid2;
    int ret;
    void * result = NULL;

    if (argc != 2)
    {
        printf("please input ./pthread_create string !\n");
        exit(EXIT_FAILURE);
    }

    if ((ret = pthread_create(&pid1, NULL, (void *)pthread_func1, (void *)argv[1])))
    {
        perror("pthread1 create error!");
        exit(EXIT_FAILURE);
    }

    if ((ret = pthread_create(&pid2, NULL, (void *)pthread_func2, (void *)argv[1])))
    {
        perror("pthread2 create error!");
        exit(EXIT_FAILURE);
    }

    printf("wait pthread finish.........!\n");
    sleep(1);

    if ((ret = pthread_join(pid1, &result)) != 0)
    {
        perror("jion pthread error!");
        exit(EXIT_FAILURE);
    }
    else
    {
        printf("pthread1: result is %s\n", (char *)result);
    }
    
    if ((ret = pthread_join(pid2, &result)) != 0)
    {
        perror("jion pthread error!");
        exit(EXIT_FAILURE);
    }
    else
    {
        printf("pthread2: result is %s\n", (char *)result);
    }
 
    return 0;
}
bxp@ubuntu:~/2022/0502$ ./pthread_create world
wait pthread finish.........!
func2 : arg is world
func1 : arg is world
pthread1: result is hello
pthread2: result is WORLD

举例三:线程中结构体的操作(赋值及传参)

注意:malloc 分配空间时,指向 malloc 的指针,千万不要变更(赋值),否则free() 会失效,段错误。

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

typedef struct menber
{
    int a;
    char b[10];
}SH_NODE;

SH_NODE node;

void *create(void *arg)
{
    node.a = ((SH_NODE *)arg)->a;
    strcpy(node.b, ((SH_NODE *)arg)->b);

    printf("new thread ... \n");
    return (void *)&node;
}

int main(int argc,char *argv[])
{
    int error;
    pthread_t tid;
    SH_NODE *k = NULL;
    SH_NODE *c = (SH_NODE *)malloc(sizeof(node));  //c 指向malloc 不要变更
    
    printf("please input number of c->a:");
    scanf("%d", &(c->a));

    printf("please input string of c->b:");
    scanf("%s", c->b);
    
    if((error = pthread_create(&tid, NULL, create, (void *)c)) != 0)
    {
        printf("new thread is not created ... \n");
        return -1;
    }
    printf("main ... \n");


    if((error = pthread_join(tid, (void *)&k)) != 0)
    {
        printf("new thread is not exit ... \n");
        return -2;
    }
   
    printf("c->a = %d  \n",k->a);
    printf("c->b = %s  \n",k->b);
    sleep(1);

    free(c);   //指针 c 千万不能变更,否则段错误
    c = NULL;

    return 0;
}
bxp@ubuntu:~/2022/0502$ ./pthread_struct 
please input number of c->a:1
please input string of c->b:bxp
main ... 
new thread ... 
k->a = 1  
k->b = bxp 

以上代码会有一个明显的缺陷:如果两个线程对同一个共享资源操作,会产生争夺,所以,可能出现两个线程同时对该共享资源进行操作,可能产生结果:heRLD (一半小写,一半大写的情况)

(如何解决这样的问题。。。 引出线程同步)

三、线程同步

进行多线程编程,因为无法知道哪个线程会在哪个时候对共享资源进行操作,因此让如何保护共享资源变得复杂,通过下面这些技术的使用,可以解决:

线程之间对资源的竞争:

(1)互斥量Mutex                

(2)信号灯Semaphore                

(3)条件变量Conditions

1、互斥量

(1)互斥量注意事项

        本质上讲,互斥量是一把锁,该锁保护一个或一些资源(内存等)。一个线程想要访问该资源,必须先获得互斥锁,将资源锁住,从而使其他线程无法使用该资源;其他线程想用该资源,必须争取互斥锁;这就是线程同步。

头文件 #include <pthread.h>

标识符:pthread_mutex_t

(2)互斥锁创建步骤

(a)创建互斥锁,使用 pthread_mutex_t 标识符

(b)初始化互斥锁,使用 PTHREAD_MUTEX_INITIALIZER (静态)  pthread_mutex_init 函数

(c)上锁,使用 pthread_mutex_lock() 函数

        .................共享资源.................

(d)解锁,使用 pthread_mutex_unlock()函数

(e)销毁锁,使用 pthread_mutex_destory()函数  

(3)互斥锁 API 使用

(a)pthread_mutex_t 标识符 创建互斥锁

#include <pthread.h>

pthread_mutex_t  [ 互斥锁名 ]

(b)pthread_mutex_init() 函数(互斥锁初始化)

#include <pthread.h>

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

//静态初始化:互斥锁 = PTHREAD_MUTEX_INITIALIZER

返回值:成功:0;失败:-1

mutex: 将互斥锁取地址(输入参数)

attr:锁的类型 (NULL (多数)普通锁:PTHREAD_MUTEX_TIMED_NP)

嵌套锁:PTHREAD_MUTEX_RECURSIVE_NP

检错锁:PTHREAD_MUTEX_ERRORCHECK_NP

自适应锁:PTHREAD_MUTEX_ADAPTIVE_NP

静态初始化:互斥锁 = PTHREAD_MUTEX_INITIALIZER

(c)pthread_mutex_lock() 函数(上锁)

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex)

//对共享资源的访问, 要使用互斥量进行加锁, 如果互斥量已经上了锁, 调用线程会阻塞, 直到互斥量被解锁。

int pthread_mutex_trylock(pthread_mutex_t *mutex)

//  trylock是非阻塞调用模式, 如果互斥量没被锁住, trylock函数将对互斥量加锁, 并获得对共享资源的访问权限; 如果互斥量被锁住了,trylock函数将不会阻塞等待而直接返回EBUSY, 表示共享资源处于忙状态

返回值:成功:0;失败:-1

mutex: 将互斥锁取地址(输入参数)

(d)pthread_mutex_unlock()函数(解锁)

#include <pthread.h>

int pthread_mutex_unlock(pthread_mutex_t *mutex)

//在操作完成后,必须给互斥量解锁,也就是前面所说的释放。这样其他等待该锁的线程才有机会获得该锁,否则其他线程将会永远阻塞。

返回值:成功:0;失败:-1

mutex: 将互斥锁取地址(输入参数)

(e)pthread_mutex_destory()函数(销毁锁)

#include <pthread.h> 

int pthread_mutex_destroy(pthread_mutex_t *mutex)

返回值:成功:0;失败:-1

mutex: 将互斥锁取地址(输入参数)

举例四:互斥锁 买票问题

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

pthread_mutex_t lock;
int ticket = 10;

void *buy_tickets(void *arg)
{
    int *ticket = NULL;
    ticket = (int *)arg;

    while (1)
    {
        pthread_mutex_lock(&lock);
        if (*ticket > 0)
        {
           
            sleep(1);
            printf("10 tickets remain is %d\n", --(*ticket));
           
        }
        pthread_mutex_unlock(&lock);
        if (*ticket <= 0)
        {
            break;
        }
        
    }
    
    return NULL;

}

int main(int argc, char *argv[])
{
    int ret;
 
    pthread_t pid1;
    pthread_t pid2;
    pthread_t pid3;

    void *return_val = NULL;

    if ((ret = pthread_mutex_init(&lock, NULL)) != 0)
    {
        perror("pthread mutex error!");
        exit(EXIT_FAILURE);
    }

    if ((ret = pthread_create(&pid1, NULL, (void *)buy_tickets, (void *)&ticket)) != 0)
    {
        perror("pthread create error!");
        exit(EXIT_FAILURE);
    }
    pthread_create(&pid2, NULL, (void *)buy_tickets, (void *)&ticket);
    pthread_create(&pid3, NULL, (void *)buy_tickets, (void *)&ticket);

    if ((ret = pthread_join(pid1, &return_val)) != 0)
    {
        perror("pthread join error!");
        exit(EXIT_FAILURE);
    }
    pthread_join(pid2, &return_val);
    pthread_join(pid3, &return_val);


    if ((ret = pthread_mutex_destroy(&lock)) != 0)
    {
        perror("pthread mutex destroy error!");
        exit(EXIT_FAILURE);
    }
    
    

    return 0;
}

2、条件变量

(1)互斥量注意事项

        互斥锁存在的问题:互斥锁一个明显的缺点是它只有锁定和非锁定两种状态。设想一种简单的情景:若多个线程访问一个共享资源,并不知道何时应该使用共享资源,如果在临界区里加入判断语句,或者可以有效,但是一来效率不高,二来在复杂的环境下难以实现,这时我们需要一个结构,能在条件成立时触发相应线程,进行变量的修改和访问。

        条件变量:条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。在使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。

头文件 #include <pthread.h>

标识符:pthread_cond_t

(2)互斥锁创建步骤

(a)创建条件变量,使用 pthread_cond_t 标识符

(b)条件变量初始化,使用 PTHREAD_COND_INITIALIZER (静态)  pthread_cond_init 函数

(c)条件变量等待,使用 pthread_cond_wait() 函数

         .......pthread_cond_signal() 函数 (发信号).........

(d)条件变量撤销,使用 pthread_cond_destroy() 函数

(3)互斥锁 API 使用

(a)pthread_cond_t 标识符(创建条件变量)

#include <pthread.h>

pthread_cond_t  [ 条件变量名 ]

(b)pthread_cond_init()函数(函数条件变量初始化)

#include <pthread.h>

int pthread_cond_init(pthread_cond_t * cond, pthread_condattr_t * cond_attr)

//静态初始化:条件变量 = PTHREAD_COND_INITIALIZER 

返回值:成功:0;失败:-1

cond: 将条件变量取地址(输入参数)

attr:条件变量的类型 (NULL (多数))

(c)pthread_cond_wait()函数(条件变量等待)

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t *mutex)

//相当于 :unlock -> wait->lock

// int pthread_cond_timedwait(pthread_cond_t *cond; pthread_mutex_t *mutex;           const struct timespec *sbstime)   有时间的等待

返回值:成功:0;失败:-1

cond: 将条件变量取地址(输入参数)

mutex: 将互斥锁取地址(输入参数)

//时间结构体

 typedef struct timespec      

{              

        time_t  tv_sec;              

        long  tv_nsec;        

} timespec_t;

pthread_cond_signal() 函数 (发信号)

#include <pthread.h> 

int pthread_cond_signal(pthread_cond_t *cond)

//int pthread_cond_broadcast(pthread_cond_t *cond);

返回值:成功:0;失败:-1

cond: 将条件变量取地址(输入参数)

(d)pthread_cond_destroy()函数(条件变量撤销)

#include <pthread.h>

int pthread_cond_destroy(pthread_cond_t *cond)

返回值:成功:0;失败:-1

cond: 将条件变量取地址(输入参数)

3、互斥锁与条件变量连用关系

线程1  

pthread_mutex_lock(&mutex);  

if(条件不成立)  

pthread_cond_wait(&cond,*mutex);  

修改条件  

pthread_mutex_unlock(&mutex);

线程2    

pthread_mutex_lock(&mutex);    

使条件满足    

pthread_cond_signal(&cond);    

pthread_mutex_unlock(&mutex);

举例五:生产者与消费者问题

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

#define MAX_SIZE 16
#define OVER -1

typedef struct producer_consumer
{
    pthread_mutex_t lock;
    pthread_cond_t nonull;
    pthread_cond_t nofull;
    int front;
    int rear;
    int buff[MAX_SIZE];
}NODE;

NODE node;

void init_function(NODE * node)
{
    int ret;

    if((ret = pthread_mutex_init(&node->lock, NULL)) != 0)
    {
        perror("mutex init error!");
        exit(EXIT_FAILURE);
    }
    
    if ((ret = pthread_cond_init(&node->nofull, NULL)) != 0)
    {
        perror("cond init error!");
        exit(EXIT_FAILURE);
    }

    if ((ret = pthread_cond_init(&node->nonull, NULL)) != 0)
    {
        perror("cond init error!");
        exit(EXIT_FAILURE);
    }

    node->front = node->rear = 0;
    

}

void put(NODE *node, int n)
{
    pthread_mutex_lock(&node->lock);
    if ((node->rear + 1) % MAX_SIZE == node->front)
    {
        pthread_cond_wait(&node->nofull, &node->lock);
    }
    node->buff[node->rear] = n;
    node->rear = (node->rear + 1) % MAX_SIZE;
    pthread_cond_signal(&node->nonull);

    pthread_mutex_unlock(&node->lock);
    
}

int get(NODE *node)
{
    int n;

    pthread_mutex_lock(&node->lock);

    if (node->front == node->rear)
    {
        pthread_cond_wait(&node->nonull, &node->lock);
    }
    n = node->buff[node->front];
    node->front = (node->front + 1) % MAX_SIZE;
    pthread_cond_signal(&node->nofull);

    pthread_mutex_unlock(&node->lock);
    
    return n;
}

void *producer(void *arg)
{
    int i;

    for (i = 0; i < 20; i++)
    {
        usleep(1000);
        printf("product %d\n", i + 1);
        put(&node, i+1);
    }
    put(&node, OVER);
    
    return NULL;
}

void *consumer(void *arg)
{
    int i;

    while (1)
    {
        sleep(1);
        i = get(&node);
        if (i == -1)
        {
            break;
        }
        
        printf("consumer %d\n", i);

    }

    return NULL;
}



int main(int argc, char *argv[])
{
    int ret;
    pthread_t pid1;
    pthread_t pid2;
    void *return_val = NULL;


    init_function(&node);

    if ((ret = pthread_create(&pid1, NULL, (void *)producer, NULL)) != 0)
    {
        perror("pthread create error!");
        exit(EXIT_FAILURE);
    }

    if ((ret = pthread_create(&pid2, NULL, (void *)consumer, NULL)) != 0)
    {
        perror("pthread create error!");
        exit(EXIT_FAILURE);
    }
    
    if ((ret = pthread_join(pid1, &return_val)) != 0)
    {
        perror("join error!");
        exit(EXIT_FAILURE);
    }

    if ((ret = pthread_join(pid2, &return_val)) != 0)
    {
        perror("join error!");
        exit(EXIT_FAILURE);
    }

    if ((ret = pthread_cond_destroy(&node.nofull)) != 0)
    {
        perror("destroy cond error!");
        exit(EXIT_FAILURE);
    }

    if ((ret = pthread_cond_destroy(&node.nonull)) != 0)
    {
        perror("destroy cond error!");
        exit(EXIT_FAILURE);
    }

    if ((ret = pthread_mutex_destroy(&node.lock)) != 0)
    {
        perror("destroy lock error!");
        exit(EXIT_FAILURE);
    }
    
    return 0;
}

  • 5
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值