操作系统中的POSIX线程
POSIX线程标准由POSIX线程遵循,有时也称为pthreads。通过使用线程,可以将单个任务分解成多个可以同时运行的独立任务,从而使程序并行化。操作系统中的线程可以是用户级线程或内核级线程,并由内核进行管理。内核级线程由操作系统管理,而用户级线程完全由应用程序控制。内核级线程包括POSIX线程。
POSIX线程标准定义了一个线程创建和操作的API。此API中的函数允许您启动新线程、修改线程属性、同步线程和终止线程。POSIX线程在许多类Unix操作系统(包括Linux、macOS和FreeBSD)以及某些其他操作系统中使用。由于其标准化的API和高效的实现,它们是创建多线程程序的流行选择。
线程的概念
线程通常是一个轻量级过程,可以在同一进程内与其他线程并行运行。所有线程共享全局变量、静态变量、堆内存和栈。虽然它们可以共享堆内存和栈,但线程有自己的栈空间。线程可以通过消息传递和共享内存来相互通信。
C语言中POSIX线程的示例代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NUM_THREADS 5
void *PrintHello(void *threadid) {
long tid;
tid = (long)threadid;
printf("Hello World! It's me, thread #%ld!\n", tid);
pthread_exit(NULL);
}
int main(int argc, char *argv[]) {
pthread_t threads[NUM_THREADS];
int rc;
long t;
for (t = 0; t < NUM_THREADS; t++) {
printf("In main: creating thread %ld
", t);
rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t);
if (rc) {
printf("ERROR; return code from pthread_create() is %d
", rc);
exit(-1);
}
}
pthread_exit(NULL);
}
输出
In main: creating thread 0 In main: creating thread 1 Hello World! It's me, thread #0! In main: creating thread 2 In main: creating thread 3 In main: creating thread 4 Hello World! It's me, thread #4! Hello World! It's me, thread #2! Hello World! It's me, thread #1! Hello World! It's me, thread #3!
在这个例子中,创建了NUM_THREADS个线程,每个线程打印一条消息。线程使用PrintHello函数启动。PrintHello函数用作启动例程,pthread_create用于在主函数中创建线程。然后,我们使用pthread_exit等待每个线程完成其任务。
在PrintHello函数中,我们将threadid输入转换为长整型数据类型,并用它来打印一条消息,标识正在发布消息的线程。最后,我们调用pthread_exit来终止线程。
pthread_create函数需要四个参数:线程启动例程(在本例中为PrintHello)、线程ID、一个指向pthread_t变量的指针,该变量将保存新线程的ID、一个指向pthread_attr_t变量的指针,该变量指定线程属性(我们传递NULL以使用默认属性),以及传递给启动例程的void指针。
如果pthread_create返回一个非零数字,则表示在创建线程时发生错误。在这种情况下,我们输出一条错误消息并执行非零状态码程序退出。
需要注意的是,线程可能不会按照特定的顺序运行。每次运行此程序时,输出消息的顺序可能看起来不同。
为了构建和链接它,您必须使用-pthread选项将程序与pthread库链接:
$ gcc -pthread example.c -o example
此程序将生成NUM_THREADS个线程,每个线程都将打印一条包含线程标识的消息。当所有线程都运行完毕后,程序结束。
线程管理
有多个函数可用于管理线程,包括:
使用pthread_join()等待线程完成。该函数的两个输入是将要加入的线程的线程ID和一个将用于存储线程退出状态的变量的引用。
第二种方法pthread_detach()允许将线程与父线程分离。当分离的线程终止时,资源会立即释放。
使用pthread_cancel()函数终止线程。当线程被取消时,它的执行立即终止。
线程同步
在多线程编程中,线程同步对于防止竞争条件和其他与并发相关的错误至关重要。Pthreads提供多种同步机制,包括:
互斥锁
互斥锁用于防止对共享资源的并发访问。互斥锁提供互斥,限制了同时可以持有互斥锁的线程数。分别使用pthread_mutex_lock()和pthread_mutex_unlock()函数来获取和释放互斥锁。
条件变量
这些变量充当事件的标志和信号。条件变量与互斥锁相关联,并且必须在等待条件变量之前锁定互斥锁。分别使用pthread_cond_wait()和pthread_cond_signal()函数来等待和发出条件变量的信号。
信号量
使用信号量来控制对共享资源的访问。信号量跟踪一个计数器,线程可以增加或减少该计数器。当计数器达到0时,等待信号量的线程会被阻塞,直到计数器被增加。使用pthread_mutex_init()、pthread_mutex_destroy()、pthread_cond_init()、pthread_cond_destroy()、sem_init()、sem_destroy()、wait()和post()函数来初始化、销毁、等待和发出同步原语的信号。
线程安全和死锁
多线程编程需要线程安全,这确保线程正确地访问共享资源。多个线程可以同时调用线程安全的函数,而不会导致竞争条件或其他与并发相关的错误。Pthreads提供多种方法来确保线程安全,包括:
互斥锁
互斥锁用于防止对共享资源的并发访问。线程可以在使用共享资源之前使用互斥锁锁定它,并在之后解锁它。这确保一次只有一个线程可以访问共享资源。
原子操作
原子操作是在单个、不可中断的步骤中执行的操作,不会受到其他线程的干扰。Pthreads提供许多原子操作,包括atomic_add()、atomic_sub()、atomic_and()、atomic_or()和atomic_xor()。
线程局部存储
可以使用此技术创建数据,但只能由创建它的线程访问。这对于存储线程特定数据(如线程ID)可能很有用,而无需使用全局或静态变量。
多线程编程中可能发生的另一个问题是死锁。当两个或多个线程都在等待另一个线程释放资源时,就会发生死锁。Pthreads提供多种方法来避免死锁,包括:
锁顺序
使用锁顺序,您可以确保按照特定顺序获取锁。通过以相同的顺序获取锁,线程可以避免可能导致死锁的循环依赖关系。
超时
超时是一种在一段时间后解锁锁的方法。通过使用超时,线程可以避免无限期地等待锁释放,这可能导致死锁。
死锁检测
此技术用于检测和解决死锁。及早检测死锁使线程能够采取适当的行动,例如释放锁,从而打破死锁。
调试和故障排除
由于线程的非确定性性质,调试和故障排除多线程应用程序可能具有挑战性。Pthreads提供多种调试和故障排除工具,包括:
线程安全调试
这种调试多线程应用程序的方法可以防止竞争条件和其他与并发相关的错误。Pthreads提供许多线程安全调试工具,包括gdb和valgrind。
调试工具
可以使用strace和ltrace等调试工具来跟踪线程执行的系统调用和库调用。通过跟踪调用,开发人员可以查找诸如竞争条件、死锁和性能瓶颈等问题。
日志记录和跟踪
这些技术用于记录线程执行期间的行为信息。通过使用日志记录和跟踪,开发人员可以识别诸如竞争条件、死锁和性能瓶颈等问题,并检查线程在执行时的行为。
结论
Pthreads是一个强大的多线程编程工具,提供了多种管理线程、同步线程和确保线程安全的技术。对于开发复杂的、多线程的应用程序,Pthreads提供了许多先进的功能,包括线程终止、线程局部存储、条件变量和读写锁。尽管构建多线程程序可能具有挑战性,但Pthreads可以帮助程序员开发可靠且高效的多线程应用程序。通过理解多线程编程的原理和实践,开发人员可以充分利用现代多核处理器的能力,设计出具有卓越性能和可扩展性的程序。
数据结构
网络
关系数据库管理系统(RDBMS)
操作系统
Java
iOS
HTML
CSS
Android
Python
C语言编程
C++
C#
MongoDB
MySQL
Javascript
PHP