文章目录
Linux 线程
线程基础
每个用户进程有自己的地址空间
系统为每个用户进程创建一个 task_struck
来描述该进程
- 该结构体中包含了一个指针指向该进程的虚拟地址空间映射表
实际上 task_struct
和地址空间映射表一起用来表示一个进程
由于进程的地址空间是私有的,因此在进程间上下切换时,系统开销比较大
为了提高系统的性能,许多操作系统规范里引入了轻量级进程的概念,也被称为线程
在同一个进程中创建的线程共享该进程的地址空间,并且和进程一样都参与内核同一的调度
Linux 里同样用 task_struct
来描述一个线程。线程和进程都参与统一的调度
进程和线程:
- 线程是参与内核调度最小基本单位,进程是拥有资源的最小基本单位
- 进程间相互独立,而同一个进程内的线程间共享进程内所有的资源
- 多线程间通信简单,但是需要对临界资源进行互斥与同步操作,多进程间通信较难
- 多线程安全性差,因为其中一个线程崩溃可能会对其他线程造成影响,多进程间相互独立,安全性高。
通常线程指的是共享相同地址空间的多个任务
使用多线程的好处
- 大大提高了任务切换的效率
- 避免了额外的 TLB & cache 的刷新
多线程通过第三方的线程库来实现
New POSIX Thread Library(NPTL)
- 是早期 Linux Threads 的改进
- 采用 1:1 的线程模型
- 显著提高了运行效率
- 信号处理效率更高
一个进程中的多个线程共享以下资源
- 可执行的指令
- 静态数据
- 进程中打开的文件描述符
- 信号处理函数
- 当前工作目录
- 用户 ID
- 用户组 ID
每个线程的私有资源如下
- 线程 ID(TID)
- PC(程序计数器)和相关寄存器
- 堆栈
- 局部变量
- 返回地址
- 错误号(
errno
) - 信号掩码和优先级
- 执行状态和属性
多线程编程
NPTL 线程库中提供了如下基本操作
- 创建线程
- 删除线程
- 控制线程
线程间同步和互斥机制
- 信号量
- 互斥锁
- 条件变量
线程相关函数是由第三方库支持,所以编译时需要加上 -lpthread
选项
create a new thread - pthread_create
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
void *thread_func(void *arg){
//printf("im %lu\n", pthread_self());
pthread_exit(NULL);}
int main(int argc, const char *argv[]){
pthread_t tid;
int i = 0;
while(1){
if(pthread_create(&tid, NULL, thread_func, NULL) != 0){
perror("fail to pthread_create");
exit(1);}
printf("tid : %d\n", ++i);
pthread_join(tid, NULL);}
while(1);
return 0;}
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
void *thread_func(void *arg){
printf("i:%d\n", *(int *)arg);
static int a = 200;
pthread_exit(&a);}
int main(int argc, const char *argv[]){
pthread_t tid;
int i = 100;
if(pthread_create(&tid, NULL, thread_func, &i) != 0){
perror("fail to pthread_create");
exit(1);}
int *p;
pthread_join(tid,(void **)&p);
printf("a : %d\n", *p);
while(1);
return 0;}
terminate calling thread - pthread_exit
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
char message[32] = "Hello World";
void *thread_function(void *arg);
int main(int argc, const char *argv[]){
pthread_t a_thread;
void *thread_result;
// 使用缺省属性创建线程
if(pthread_create(&a_thread, NULL, thread_function, (void *)message) < 0){
perror("fail to pthread_create");
exit(-1);}
printf("waiting for thread tp finish\n");
// 等待线程结束
if(pthread_join(a_thread, &thread_result) < 0){
perror("fail to pthread_join");
exit(-1);}
printf("thread_result is %s\n", (char *)thread_result);
printf("MESSAGE is now %s\n", message);
return 0;}
void *thread_function(void *arg){
printf("thread_function is running, argument is %s\n", (char *)arg);
strcpy(message, "marked by thread");
pthread_exit("Thank you for the cpu time");}
$ gcc ./sample.c -lpthread
$ ./a.out
waiting for thread tp finish
thread_function is running, argument is Hello World
thread_result is Thank you for the cpu time
MESSAGE is now marked by thread
Linux 线程同步和互斥
线程间机制
- 多线程共享同一个进程的地址空间
- 优点:线程间很容易进行通信
- 通过全局变量实现数据共享和交换
- 缺点:多个线程同时访问共享对象时需要引入同步和互斥机制
信号量
线程间同步
同步(synchronization)指的是多个任务(线程)按照约定的顺序相互组合完成一件事情
1968年,Edsgar Dijkstra 基于信号量的概念提出了一种同步机制
由信号量来决定线程时继续运行还是阻塞等待
线程间同步 - P/V 操作
信号量代表某一类资源,其值表示系统中该资源的数量
信号量是一个受保护的变量,只能通过三种操作来访问
- 初始化
- P 操作(申请资源)
- V 操作(释放资源)
信号量的值为非负整数
P(S)含义如下
-
if (信号量的值大于0){ 申请资源的任务继续运行; 信号量的值减一;} else{申请资源的任务阻塞;}
V(S)含义如下
-
if(没有任务在等待该资源){信号量的值加一;} else{唤醒第一个等待的任务,让其继续运行}
Posix Semaphore API
posix 中定义了两类信号量
- 无名信号量(基于内存的信号量)
- 有名信号量
Pthread
库常用的信号量操作函数
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_wait(sem_t *sem)
int sem_post(sem_t *sem)
线程同步示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
char buf[60];
sem_t sem;
void *function(void *arg);
int main(){
pthread_t a_thread;
void *thread_result;
if(sem_init(&sem, 0, 0) < 0){
perror("fail to sem_init\n");
exit(-1);}
if(pthread_create(&a_thread, NULL, function, NULL) < 0){
perror("fail to pthread_create\n");
exit(-1);}
printf("input 'quit' to exit\n");
do{
fgets(buf, 60, stdin);
sem_post(&sem);
}while(strncmp(buf, "quit", 4) != 0);
return 0;}
void *function(void *arg){
while(1){
sem_wait(&sem);
printf("You enter %d characters\n", strlen(buf)-1);
}
}
互斥锁
线程间互斥
- 引入互斥(mutual exclusion)锁的目的是用来保证共享数据操作的完整性。
- 互斥锁主要用来保护临界资源
- 每个临界资源都由一个互斥锁来保护,任何时刻最多只能有一个线程能访问该资源
- 线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止
Posix Mutex API
- destroy and initialize a mutex -
pthread_mutex_init
- lock a mutex -
pthread_mutex_lock
- unlock a mutex -
pthread_mutex_unlock
- destroy and initialize a mutex -
pthread_mutex_destroy
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<string.h>
unsigned int value1, value2, count;
pthread_mutex_t mutex;
void *function(void *arg);
int main(int argc, char *argv[]){
pthread_t a_thread;
if(pthread_mutex_init(&mutex, NULL) < 0){
perror("fail to mutex_init");
exit(-1);}
if(pthread_create(&a_thread, NULL, function, NULL) < 0){
perror("fail to pthread_create");
exit(-1);}
while(1){
count++;
#ifdef _LOCK_
pthread_mutex_lock(&mutex);
#endif
value1 = count;
value2 = count;
#ifdef _LOCK_
pthread_mutex_unlock(&mutex);
#endif
}
return 0;}
void *function(void *arg){
while(1){
#ifdef _LOCK_
pthread_mutex_lock(&mutex);
#endif
if(value1 != value2){
printf("count = %d, value1 = %d, value2 = %d\n", count, value1, value2);
usleep(100000);}
#ifdef _LOCK_
pthread_mutex_unlock(&mutex);
#endif
}
return NULL;}
条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);