目录
一.什么是线程?
在Linux中,线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。同一进程中的多条线程将共享该进程所拥有的全部资源,但每条线程都有各自的调用栈和程序计数器,能够独立运行。
那么什么是并发与并行呢?
并发(Concurrency)
并发是指系统能够处理多个同时发生的任务。在并发编程中,多个任务在同一时间段内执行,但它们可能不是同时执行。并发可以通过时间分片(time slicing)来实现,操作系统快速地在多个任务之间切换,给人一种同时执行的错觉。
并发的主要目的是提高资源的利用率和系统的响应速度。例如,在一个Web服务器中,可以同时处理多个用户的请求,这样就可以更有效地利用服务器的资源,并缩短用户的等待时间。
并行(Parallelism)
并行是指系统能够在同一时刻执行多个任务。并行编程需要多个处理器或者多个核心,每个处理器或核心可以独立地执行一个任务。并行是真正的“同时”执行,它通过增加计算资源来提高性能。
并行的主要目的是通过同时执行多个任务来提高性能和缩短任务的完成时间。例如,在一个多核处理器上,可以同时运行多个线程或者进程,这样可以显著减少执行复杂计算或者数据处理任务所需的时间。
1.1 线程的概念
- 轻量级进程(Lightweight Process, LWP):线程又被称为轻量级进程,因为线程的创建、撤销和切换比进程更快。
- 线程的属性:线程具有就绪、阻塞和运行三种基本状态,以及创建和终止两种辅助状态。
- 线程的执行:线程可以并发执行,这意味着它们似乎是在同时运行,但实际上是在CPU时间片轮转机制下快速切换执行。
如何使用线程:
在Linux中,线程可以通过 POSIX 线程(pthread)库来使用。这个库提供了一系列函数来创建、同步和管理线程。
线程共享资源
1.文件描述符表
2.每种信号的处理方式
3.当前工作目录
4.用户ID和组ID
5.内存地址空间 (.text/.data/.bss/heap/共享库)
线程非共享资源1.线程id
2.处理器现场和栈指针(内核栈)
3.独立的栈空间(用户空间栈)
4.errno变量
5.信号屏蔽字
6.调度优先级线程优、缺点
- 优点: 1. 提高程序并发性 2. 开销小 3. 数据通信、共享数据方便
- 缺点: 1. 库函数,不稳定 2. 调试、编写困难、gdb不支持 3. 对信号支持不好
- 优点相对突出,缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大。
1.2 线程的基本函数
基本函数介绍和使用:
线程创建:
pthread_create
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
thread
:指向线程标识符的指针。attr
:线程属性,通常设为NULL
表示使用默认属性。start_routine
:线程运行函数的起始地址。arg
:运行函数的参数。线程终止:
pthread_exit
void pthread_exit(void *retval);
retval
:线程的返回值。等待线程终止:
pthread_join
int pthread_join(pthread_t thread, void **retval);
thread
:等待终止的线程标识符。retval
:存储线程返回值的指针。线程取消:
pthread_cancel
int pthread_cancel(pthread_t thread);
thread
:要取消的线程标识符。线程互斥锁:
pthread_mutex_lock/unlock
int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);
mutex
:互斥锁变量,用于同步线程对共享资源的访问。
1.3 线程的基本使用例子:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
// 线程运行函数
void *print_message_function(void *ptr) {
char *message;
message = (char *) ptr;
printf("%s \n", message);
return NULL;
}
int main() {
pthread_t thread1, thread2;
char *message1 = "Thread 1";
char *message2 = "Thread 2";
// 创建线程1
if(pthread_create(&thread1, NULL, print_message_function, (void*) message1)) {
printf("Error creating thread 1\n");
return 1;
}
// 创建线程2
if(pthread_create(&thread2, NULL, print_message_function, (void*) message2)) {
printf("Error creating thread 2\n");
return 1;
}
// 等待线程终止
if(pthread_join(thread1, NULL)) {
printf("Error joining thread 1\n");
return 2;
}
if(pthread_join(thread2, NULL)) {
printf("Error joining thread 2\n");
return 2;
}
return 0;
}
在这个例子中:
- 定义了一个
print_message_function
函数,它接收一个指向字符数组的指针,并打印出来。- 在
main
函数中,创建了两个线程thread1
和thread2
,它们都执行print_message_function
函数,但分别传递了不同的消息。- 使用
pthread_create
创建线程,使用pthread_join
等待线程结束。程序执行时,会创建两个线程,它们可能会并发地运行,打印出各自的消息。使用
pthread_join
确保了主程序会等待这两个线程完成它们的任务后再退出。
二.线程的属性
线程属性(Thread Attributes)是可以用来定制线程的各种特性的集合。在创建线程时,可以指定线程的属性,如果不指定,线程会使用默认属性。线程属性通过
pthread_attr_t
结构体来表示,并且在使用前需要初始化。常见的线程属性包括:
- detachstate:线程的分离状态属性,可以设置为
PTHREAD_CREATE_JOINABLE
(默认值,可以调用pthread_join
来等待线程结束)或PTHREAD_CREATE_DETACHED
(线程一旦结束,其资源立即被回收,不能被等待)。- schedpolicy:线程的调度策略,可以是
SCHED_OTHER
(默认值,普通轮转调度),SCHED_FIFO
(先来先服务调度),或SCHED_RR
(时间片轮转调度)。- inheritsched:线程的继承调度策略,可以设置为
PTHREAD_EXPLICIT_SCHED
(显式指定线程的调度属性)或PTHREAD_INHERIT_SCHED
(继承创建它的线程的调度属性,默认值)。- scope:线程的作用域,可以设置为
PTHREAD_SCOPE_SYSTEM
(与系统范围内的线程竞争CPU时间)或PTHREAD_SCOPE_PROCESS
(仅与同一进程内的线程竞争CPU时间,通常是默认值)。
2.1线程属性使用例子
#include <pthread.h>
int main() {
pthread_attr_t attr;
pthread_attr_init(&attr); // 初始化线程属性
// 设置线程为分离状态
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// 使用线程属性创建线程
pthread_t thread;
pthread_create(&thread, &attr, thread_function, NULL);
// 清理线程属性
pthread_attr_destroy(&attr);
// ... 其他代码 ...
return 0;
}
三.线程互斥
3.1互斥锁
互斥锁(Mutex):
互斥锁是一种同步机制,用于防止多个线程同时访问共享资源。在多线程环境中,当多个线程尝试同时访问同一资源时,互斥锁确保任何时刻只有一个线程能够访问该资源。
3.2互斥锁常用函数
常用的互斥锁函数:
初始化互斥锁:
pthread_mutex_init
c
复制
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
mutex
:指向互斥锁变量的指针。attr
:互斥锁属性,通常设为NULL
表示使用默认属性。锁定互斥锁:
pthread_mutex_lock
c
复制
int pthread_mutex_lock(pthread_mutex_t *mutex);
mutex
:指向互斥锁变量的指针。尝试锁定互斥锁:
pthread_mutex_trylock
c
复制
int pthread_mutex_trylock(pthread_mutex_t *mutex);
mutex
:指向互斥锁变量的指针。如果互斥锁已经被锁定,函数不会阻塞,而是返回错误码。解锁互斥锁:
pthread_mutex_unlock
c
复制
int pthread_mutex_unlock(pthread_mutex_t *mutex);
mutex
:指向互斥锁变量的指针。销毁互斥锁:
pthread_mutex_destroy
c
复制
int pthread_mutex_destroy(pthread_mutex_t *mutex);
mutex
:指向互斥锁变量的指针。
3.3互斥锁使用例子
#include <pthread.h>
// 全局变量
pthread_mutex_t lock; // 互斥锁
int shared_data = 0; // 共享数据
// 线程函数
void *thread_function(void *arg) {
pthread_mutex_lock(&lock); // 加锁
shared_data++; // 访问共享数据
pthread_mutex_unlock(&lock); // 解锁
return NULL;
}
int main() {
pthread_mutex_init(&lock, NULL); // 初始化互斥锁
pthread_t threads[10];
for(int i = 0; i < 10; i++) {
pthread_create(&threads[i], NULL, thread_function, NULL); // 创建线程
}
for(int i = 0; i < 10; i++) {
pthread_join(threads[i], NULL); // 等待线程结束
}
pthread_mutex_destroy(&lock); // 销毁互斥锁
printf("Shared data: %d\n", shared_data); // 输出共享数据的最终值
return 0;
}
在这个例子中,我们创建了一个互斥锁
lock
来保护对shared_data
的访问。每个线程在访问shared_data
之前都会尝试锁定互斥锁,完成访问后解锁。这样可以确保在任一时刻只有一个。