Linux 进程间通信_14-线程
线程基础
-
每个用户进程有自己的地址空间
-
系统为每个用户进程创建一个 task_struct来描述该进程
-
该结构体中包含了一个指针指向该进 程的虚拟地址空间映射表
-
实际上task_struct 和地址空间映射表一起用来表示一个进程
-
由于进程的地址空间是私有的,因此在进程间上下文切换时,系统开销比较大
-
为了提高系统的性能,许多操作系统规范里引入了轻量级进程的概念,也被称为线程
-
在同一个进程中创建的线程共享该进程的地址空间
-
Linux里同样用task_struct来描述一个线程。线程和进程都参与统一的调度
-
通常线程指的是共享相同地址
空间的多个任务 -
使用多线程的好处
大大提高了任务切换的效率
避免了额外的TLB & cache的刷新
一个进程中的多个线程共享以下资源
- 可执行的指令
- 静态数据
- 进程中打开的文件描述符
- 信号处理函数
- 当前工作目录
- 用户ID
- 用户组ID
每个线程私有的资源如下
- 每个线程私有的资源如下
- 线程ID (TID)
- PC(程序计数器)和相关寄存器
- 堆栈
局部变量
返回地址 - 错误号 (errno)
- 信号掩码和优先级
- 执行状态和属性
多线程编程
pthread线程库中提供了如下基本操作
-
创建线程
-
回收线程
-
结束线程
线程 – 示例
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
char message[32] = "Hello World";
void *thread_func(void *arg);
int main(void)
{
pthread_t a_thread;
void *result;
//创建线程
if (pthread_create(&a_thread, NULL, thread_func, NULL) != 0)
{
printf("fail to pthread_create\n");
exit(-1);
}
//回收线程
pthread_join(&a_thread, &result);
printf("result is %s\n", result);
printf("message is %s\n", message);
return 0;
}
void *thread_func(void *arg)
{
sleep(1);
strcpy(message, "marked by thread");
pthread_exit("thank you for waiting for me");
}
result is thank you for waiting for me
message is marked by thread
线程间同步和互斥机制
- 多线程共享同一个进程的地址空间
- 优点:线程间很容易进行通信,通过全局变量实现数据共享和交换
- 缺点:多个线程同时访问共享对象时需要引入同步和互斥机制
线程间同步
-
同步(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); // P操作
- int sem_post(sem_t *sem); // V操作
- int sem_trywait(sem_t *sem);
- int sem_getvalue(sem_t *sem, int
*svalue);
信号量初始化 – sem_init
信号量 – P / V 操作
-
线程同步示例1
两个线程同步读写缓冲区(生产者/消费者问题)#include <stdio.h> #include <string.h> #include <pthread.h> #include <semaphore.h> #include <stdlib.h> char buf[32]; sem_t sem; void *function(void *arg); int main(void) { pthread_t a_thread; if (sem_init(&sem, 0, 0) < 0) { perror("sem_init"); exit(-1); } if (pthread_create(&a_thread, NULL, function, NULL) != 0) { printf("fail to pthread_create\n"); exit(-1); } printf("input 'quit' to exit\n"); do { fgets(buf, 32, stdin); //V操作 sem_post(&sem); } while (strncmp(buf, "quit", 4) != 0); return 0; } void *function(void *arg) { while ( 1 ) { // P操作 sem_wait(&sem); printf("you enter %d characters\n", strlen(buf)); } }
-
线程同步示例2
#include <stdio.h> #include <string.h> #include <pthread.h> #include <semaphore.h> #include <stdlib.h> char buf[32]; sem_t sem_r, sem_w; void *function(void *arg); int main(void) { pthread_t a_thread; if (sem_init(&sem_r, 0, 0) < 0) { perror("sem_init"); exit(-1); } if (sem_init(&sem_w, 0, 1) < 0) { perror("sem_init"); exit(-1); } if (pthread_create(&a_thread, NULL, function, NULL) != 0) { printf("fail to pthread_create\n"); exit(-1); } printf("input 'quit' to exit\n"); do { sem_wait(&sem_w); fgets(buf, 32, stdin); sem_post(&sem_r); } while (strncmp(buf, "quit", 4) != 0); return 0; } void *function(void *arg) { while ( 1 ) { sem_wait(&sem_r); printf("you enter %d characters\n", strlen(buf)); sem_post(&sem_w); } }
线程间互斥
-
临界资源
一次只允许一个任务(进程、线程)访问的共享资源 -
临界区
访问临界区的代码 -
互斥机制
mutex互斥锁
任务访问临界资源前申请锁,访问完后释放锁 -
引入互斥(mutual exclusion)锁的目的是用来保证共享数据操作的完整性。
-
互斥锁主要用来保护临界资源
-
每个临界资源都由一个互斥锁来保护,任何时刻最多只能有一个线程能访问该资源
-
线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止
互斥锁初始化 – pthread_mutex_init
申请锁 – pthread_mutex_lock
释放锁 – pthread_mutex_unlock
线程互斥 – 示例
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
unsigned int count, value1, value2;
pthread_mutex_t lock;
void *function(void *arg);
int main(void)
{
pthread_t a_thread;
if (pthread_mutex_init(&lock, NULL) != 0)
{
printf("fail to pthread_mutex_init\n");
exit(-1);
}
if (pthread_create(&a_thread, NULL, function, NULL) != 0)
{
printf("fail to pthread_create");
exit(-1);
}
while ( 1 )
{
count++;
#ifdef _LOCK_
pthread_mutex_lock(&lock);
#endif
value1 = count;
value2 = count;
#ifdef _LOCK_
pthread_mutex_unlock(&lock);
#endif
}
return 0;
}
void *function(void *arg)
{
while ( 1 ) {
#ifdef _LOCK_
pthread_mutex_lock(&lock);
#endif
if (value1 != value2)
{
printf("value1 = %u, value2 = %u\n", value1, value2);
usleep(100000);
}
#ifdef _LOCK_
pthread_mutex_unlock(&lock);
#endif
}
return NULL;
}
不使用互斥锁
$ gcc –o test test.c -lpthread
$ ./test
value1 = 19821, value2 = 19820
value1 = 14553456, value2 = 14553455
value1 = 29196032, value2 = 29196031
……
使用互斥锁
$ gcc –o test test.c –lpthread –D_LOCK_
$ ./test