线程概述
概念
进程与线程的区别
- 进程间难以共享,线程可共享
- 创建进程代价高,线程代价低
线程和进程的虚拟地址空间
线程之间共享与非共享资源
NPTL 有关介绍
线程操作函数
创建线程—— pthread_create函数
- 一般情况下,main函数所在的线程我们称之为主线程(main线程),其余创建的线程
称之为子线程。 - 程序中默认只有一个进程,fork()函数调用,2个进程
- 程序中默认只有一个线程,pthread_create()函数调用,2个线程。
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
- 功能:创建一个子线程
- 参数:
thread:传出参数,线程创建成功后,子线程的线程ID被写到该变量中。
attr : 设置线程的属性,一般使用默认值,NULL
start_routine : 函数指针,这个函数是子线程需要处理的逻辑代码
arg : 给第三个参数使用,传参 - 返回值:
成功:0
失败:返回错误号。这个错误号和之前errno不太一样。
获取错误号的信息: 在 #include <string.h> 下的
char * strerror(int errnum);
示例代码
- 注意传入参数的数据类型
void*
- 休眠1s 是为了 防止程序过快结束,子线程还为创建调用成功,或者
sleep(1);
//可以改为pthread_exit()
函数 终止主进程 - 注意gcc编译链接过程中的语句 后缀要加个
-pthread
或者-lpthread
,(静态库、动态库中的概念)
#include <pthread.h>
#include<stdio.h>
#include <unistd.h>
#include <string.h>
void * myCallback(void * arg) {
printf("I am Child thread..\n");
printf("recv:%d\n",*(int *)arg);//注意数据类型
return arg;
}
int main() {
pthread_t pthreadID;
int num =10;//用于测试pthread_create 最后一个形参,传递参数
//创建子线程
int ret = pthread_create(&pthreadID,NULL,myCallback,&num);
if(ret == -1) {
int _errno;
char * errstr = strerror(_errno);
printf("erroinfo:%s",errstr);
}
//主线程打印 1~10
for(int i = 0 ;i<10;i++) {
printf("%d\n",i);
}
//休眠1s 用于 子线程创建,然后调用回调函数
sleep(1);//可以改为 pthread_exit() 函数 终止主进程
return 0;
}
终止线程—— pthread_exit() 函数
#include <pthread.h>
void pthread_exit(void *retval);
- 功能:终止一个线程,在哪个线程中调用,就表示终止哪个线程
- 参数:
retval:需要传递一个指针,作为一个返回值,可以在pthread_join()中获取到。
获取线程ID—— pthread_self() 函数
pthread_t pthread_self(void);
- 功能:获取当前的线程的线程ID
比较线程ID(多用于跨平台)—— pthread_equal() 函数
- int pthread_equal(pthread_t t1, pthread_t t2);
功能:比较两个线程ID是否相等 - 不同的操作系统,pthread_t类型的实现不一样,有的是无符号的长整型,有的
是使用结构体去实现的。
连接已终止线程 pthread_join()函数
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
- 功能:和一个已经终止的线程进行连接
回收子线程的资源
这个函数是阻塞函数,调用一次只能回收一个子线程
一般在主线程中使用 - 参数:
thread:需要回收的子线程的ID
retval: (注意是void**)接收子线程退出时的返回值(就是pthread_exit(void *retval) 中的retval) - 返回值:
0 : 成功
非0 : 失败,返回的错误号 - 为什么是 void *? 这样可以修改 int ,详见:探讨pthread_join 第二参数是void**.
分离线程 pthread_detach()
#include <pthread.h>
int pthread_detach(pthread_t thread);
- 功能:分离一个线程。被分离的线程在终止的时候,会自动释放资源返回给系统。
1.不能多次分离,会产生不可预料的行为。
2.不能去连接一个已经分离的线程,会报错。 - 参数:需要分离的线程的ID
- 返回值:
成功:0
失败:返回错误号
取消线程 pthread_cancle()
#include <pthread.h>
int pthread_cancel(pthread_t thread);
-
功能:取消线程(让指定ID的线程终止)
取消某个线程可以终止这个线程,但不是立马终止,而是当子进程执行到一个取消点时线程才会终止 -
取消点:系统规定好的一些调用,可以理解为从用户区到内核区的切换点称为取消点。
线程属性 相关函数
-
初始化线程属性变量
int pthread_attr_init(pthread_attr_t *attr); -
释放线程属性的资源
int pthread_attr_destroy(pthread_attr_t *attr); -
获取线程分离的状态属性
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate); -
设置线程分离的状态属性
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
线程同步
- 线程主要优势:可共同使用全局变量,但代价是必须确保线程不会同时操作(读写)同一变量。
- 临界区:指访问某一共享资源的 代码段,代码的执行应为原子操作,也就是同时访问同一共享资源的其他线程不应终断该片段执行。
- 线程同步:即当有一个线程对内存进行操作时,其他线程都不可以对这个内存地址进行操作,进入等待状态,知道线程对内存操作结束。
互斥锁
互斥量的类型 pthread_mutex_t
- 初始化互斥量
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
- 参数 :
- mutex : 需要初始化的互斥量变量
- attr : 互斥量相关的属性,NULL
- restrict : C语言的修饰符,被修饰的指针,不能由另外的一个指针进行操作。
pthread_mutex_t *restrict mutex = xxx;
pthread_mutex_t * mutex1 = mutex;
- 释放互斥量的资源
int pthread_mutex_destroy(pthread_mutex_t *mutex);
- 加锁,阻塞的,如果有一个线程加锁了,那么其他的线程只能阻塞等待
int pthread_mutex_lock(pthread_mutex_t *mutex);
- 尝试加锁,如果加锁失败,不会阻塞,会直接返回。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
- 解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
死锁
- 死锁就是 A等着B 解锁 B等着A解锁 互相等待的状态
- 发生死锁的三种场景
- 忘记释放锁
- 重复加锁
- 多线程多锁,抢占锁资源
读写锁
- 特点
- 如果有其他线程读数据,则允许其他线程执行读操作,但不允许写操作
- 如果有其他线程写数据,则其他线程都不允许读、写操作
- 写是独占的,写的优先级高
相关函数
注意方框的不同。
条件变量
条件变量的类型 pthread_cond_t
- 初始化
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
- 销毁
int pthread_cond_destroy(pthread_cond_t *cond);
- 等待,调用了该函数,线程会阻塞。
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
- 等待多长时间,调用了这个函数,线程会阻塞,直到指定的时间结束。
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
- 唤醒一个或者多个等待的线程
int pthread_cond_signal(pthread_cond_t *cond);
- 唤醒所有的等待的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
信号量
-
信号量的类型
sem_t
-
int sem_init(sem_t *sem, int pshared, unsigned int value);
-
初始化信号量
-
参数:
-
sem : 信号量变量的地址
-
pshared : 0 用在线程间 ,非0 用在进程间
-
value : 信号量中的值
int sem_destroy(sem_t *sem);
- 释放资源int sem_wait(sem_t *sem);
- 对信号量加锁,调用一次对信号量的值-1,如果值为0,就阻塞
-
-
int sem_trywait(sem_t *sem);
-
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
-
int sem_post(sem_t *sem);
- 对信号量解锁,调用一次对信号量的值+1
-
int sem_getvalue(sem_t *sem, int *sval);
-
伪代码 (模拟生产者和消费者模型)
sem_t psem;
sem_t csem;
//初始化
init(psem, 0, 8);
init(csem, 0, 0);
producer() {
sem_wait(&psem);//为零阻塞 不为零则如同减去1
sem_post(&csem);//加一
}
customer() {
sem_wait(&csem);//为零阻塞 不为零则如同减去1
sem_post(&psem);//加一
}