目录
(2)、互斥锁的初始化——pthread_mutex_init()
(4)、释放锁——pthread_mutex_unlock()
一、什么是线程
在进行进程切换时,需要不断刷新cache缓存,为了减少cache刷新时的资源消耗,我们引入了轻量级进程——线程。
进程称为最小的资源分配单位,线程称为CPU最小的任务调度单位。
二、线程的特点
1、同一个进程创建的多个线程,共用同一个进程的地址空间。
2、进程创建出线程后,我们把原本进程也称为线程,并且为主线程。
三、线程的接口函数
1、创建线程:pthread_create()
头文件:
#include <pthread.h>
函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
参数:
thread:线程对象,一个线程对应一个线程对象
attr:线程属性,填NULL表示使用默认属性
start_routine:线程处理函数
rg:给线程处理函数start_routine传参,如果线程处理函数没有参数,则填NULL;返回值:
成功返回0,失败返回
注意:
在编译和线程操作相关的程序时,需要链接线程库(-lpthname)
2、结束进程:pthread_exit()
头文件:
#include <pthread.h>
函数原型:
void pthread_exit(void *retval);
参数:
retvar:表示线程结束信息,由pathread_join等待接收,如果不想返回,填NULL
3、等待线程:pthread_join()
头文件:
#include <pthread.h>
函数原型:
int pthread_join(pthread_t thread, void **retval);
等待线程一般在主线程中调用
四、进程间通信
1、基本概念
线程间通信只需要利用全局变量就可以实现。
2、要解决的问题
在一个线程使用全局变量时,有可能其他线程也在访问该数据,那么某一线程就可能遭到破坏。
五、解决方法
1、同步
多个线程之间按照事先约定好的顺序有先后的完成某个事件,这种方式我们称为同步。
(1)、信号量的概念
信号量是系统中的一种资源,本质上是一个非负整数,信号量的值等于资源的个数。
操作信号量只能由特定函数接口才能访问:
1、信号量的初始化
sem_init()
2、P操作(申请资源)
sem_wait
3、V操作(释放资源)
sem_post
(2)、信号量的初始化——sem_init()
头文件:
#include <semaphore.h>
函数原型:
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
sem:信号量对象
pshared:填0表示用于线程间通信
value:信号量的初始值
返回值:
成功返回0,失败返回-1用法:
一般用于线程声明前
(3)、P操作——sem_wait()
头文件:
#include <semaphore.h>
函数原型:
int sem_wait(sem_t *sem);
用法:
if(是否有资源){ 执行后续代码; 信号量-1; }else{ 阻塞等待,直到有资源唤醒为止; }
(4)、V操作——sem_post()
头文件:
#include <semaphore.h>
函数原型:
int sem_post(sem_t *sem);
用法:
信号量+1; if(有等待资源的程序){ 将其唤醒; }
2、互斥
当一个线程使用公共数据时,其它线程都不能访问该公共数据
(1)、互斥锁的概念
临界资源:多个线程能够共同访问的数据
临界区:涉及到临界资源的代码模块区
互斥是使用互斥锁保护临界区互斥锁就是临界区调用临界资源时将该临界资源上锁,当其它临界区访问该临界资源时,将不能访问,每一次上锁操作完临界资源后,我们都需要进行解锁操作,否则后续将不能访问该临界资源。
操作互斥锁需要几个特定的函数。
(2)、互斥锁的初始化——pthread_mutex_init()
头文件:
#include <pthread.h>
函数原型:
int pthread_mutex_init(pthread_mutex_t *mutex,pthread_mutexattr_t *attr);
参数:
mutex:互斥锁对象
attr:互斥锁使用,填NULL使用缺省属性
返回值:成功返回0,失败返回-1
(3)、申请锁——pthread_mutex_lock()
头文件:
#include <pthread.h>
函数原型:
pthread_mutex_lock(pthread_mutex_t *mutex)
(4)、释放锁——pthread_mutex_unlock()
头文件:
#include <pthread.h>
函数原型:
pthread_mutex_unlock(pthread_mutex_t *mutex)
六、代码实例
1、以下为同步使用信号量实现的方法
/*===============================================================
* Copyright (C) 2022 All rights reserved.
*
* 文件名称:test.c
* 创 建 者:QiuCC
* 创建日期:2022年08月08日
* 描 述:同步实现线程间通信
*
* 更新日志:
*
================================================================*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <strings.h>
#include <unistd.h>
#define N 100
char ch = 'a';
void *func1(void *arg);//功能函数1
void *func2(void *arg);//功能函数2
sem_t sem1;//创建第一个信号量sem1
sem_t sem2;//创建第二个信号量sem2
int main(int argc, char *argv[])
{
pthread_t thread1, thread2;//定义线程thread1和线程thread2
int ret1 = pthread_create(&thread1, NULL, func1, NULL);//创建线程thread1
if(ret1 < 0){
perror("pathread_create");
exit(-1);
}
int ret2 = pthread_create(&thread2, NULL, func2, NULL);//创建线程thread2
if(ret2 < 0){
perror("pathread_create");
exit(-1);
}
sem_init(&sem1, 0, 0);//将信号量sem1置为线程间通信,初值为0
sem_init(&sem2, 0, 1);//将信号量sem2置为线程间通信,初值为1
pthread_join(thread1, NULL);//等待线程thread1结束
pthread_join(thread2, NULL);//等待线程thread2结束
return 0;
}
//函数功能实现输入一个字符给ch
void *func1(void *arg)
{
while(1){
sem_wait(&sem2);//初始值为1,执行; func2执行完,也修改为1,执行; 若func2没有执行完,则等待
printf("please input a char\n");
ch = getchar();
getchar();
sem_post(&sem1);//将sem1的初始值0修改为1,执行功能2
}
}
//函数功能实现打印ch的内容
void *func2(void *arg)
{
while(1){
sem_wait(&sem1);//func1中将该值修改为了1,执行打印功能,若func1没有执行完,则等待
printf("the char is\n");
putchar(ch);
printf("\n\n");
sem_post(&sem2);//将sem2的初始值0修改为1,执行功能1
}
}
该案例如果不使用同步的方法来操作这两个线程,那么终端就会一直进行打印,也可以进行终端的输入,输入后打印结果变为我们输入的结果,但依旧一直循环打印。
我们使用了同步信号量来操作这两个线程后实现了输入终端的等待显示和输入一个输出一个的理想目的。
2、以下为互斥使用互斥锁的实现方法
/*===============================================================
* Copyright (C) 2022 All rights reserved.
*
* 文件名称:test.c
* 创 建 者:QiuCC
* 创建日期:2022年08月08日
* 描 述:使用互斥锁实现线程间通信
*
* 更新日志:
*
================================================================*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <strings.h>
#include <unistd.h>
#define N 100
//全局变量定义
int num1 = 0;
int num2 = 0;
int count = 0;
//定义互斥锁对象
pthread_mutex_t lock;
//功能函数声明
void *func1(void *arg);
int main(int argc, char *argv[])
{
pthread_t thread1;//thread1定义
int ret1 = pthread_mutex_init(&lock, NULL);//创建互斥锁lock
if(ret1 == -1){
perror("ret1");
exit(-1);
}
ret1 = pthread_create(&thread1, NULL, func1, NULL);//创建线程thread1
if(ret1 < 0){
perror("pathread_create");
exit(-1);
}
//该while循环为我们需要操作的函数模块
while(1){
pthread_mutex_lock(&lock);//上锁
printf("main\n");
num1 = count;//如果不加锁,当函数运行到这里是时间片(该部分线程代码使用CPU的时间)可能会在此结束,此时num1和num2不相等
num2 = count;
count++;
sleep(1);
pthread_mutex_unlock(&lock);//解锁
}
pthread_join(thread1, NULL);//等待进程结束
return 0;
}
void *func1(void *arg)
{
while(1){
pthread_mutex_lock(&lock);//由于目前有线程在使用临界资源,我们需要对其进行访问(可理解为有人上厕所,你需要敲门),如果上一个线程结束使用,你才可以使用该资源
if(num1 != num2){
printf("num1 = %d num2 = %d\n", num1, num2);
}
pthread_mutex_unlock(&lock);//解锁
}
}
该案例中如果我们不使用互斥锁操作主线程和子线程,那么当主线程运行到num1 = count时就有可能结束主线程,此时num1和num2不相等,子线程func就会对其进行打印。
使用互斥锁操作之后的程序就不会出现上述情况。