线程与进程的区别和联系
目的
为了提高计算机的吞吐量。
区别
-
内存空间:
- 进程:独享内存空间。
- 线程:共享内存空间。
-
切换开销:
- 进程之间切换:需要时间开销(上下文切换),包括建立进程资源表项、开辟内存空间、打开文件描述符等。
- 线程之间切换:不需要时间开销。
-
执行效率与安全性:
- 线程执行效率比进程要高。
- 但进程安全性比线程要高,因为一个线程异常退出,会导致整个进程退出,而进程在安全模式下异常退出,其他进程不受影响。
-
单位:
- 进程:是资源管理的最小单位。
- 线程:是执行流的最小单位。
关系
- 一段程序,至少包括一个进程,而一个进程至少包括一个线程。
- 识别进程的标识是进程号:
getpid()
- 识别线程的标识是线程号:
pthread_self()
=>unsigned int
- 是进程包括线程,而不是线程包括进程。
线程属性
- 动态性
- 并发性
- 异步性
- 共享性
注意
- 线程属于第三方库,则在编译的时候需要指定库名:
-lpthread
:线程库-lcrypt
:MD5密码库-lm
:数学库
创建线程
在C语言中,创建线程可以使用pthread_create
函数,其原型如下:
int pthread_create(pthread_t *restrict thread,
const pthread_attr_t *restrict attr,
void *(*start_routine)(void*),
void *restrict arg);
void* thread_function(void* arg) {
// 打印消息
printf("Hello from the new thread!\n");
// 线程可以在这里执行其他任务
// 线程函数返回NULL
return NULL;
}
int main() {
pthread_t thread_id; // 用于存储线程ID
int result; // 用于存储pthread_create的返回值
// 创建线程
result = pthread_create(&thread_id, NULL, thread_function, NULL);
// 检查线程是否创建成功
if (result != 0) {
perror("Thread creation failed");
return 1;
}
// 等待线程结束
pthread_join(thread_id, NULL);
printf("Thread finished executing\n");
return 0;
}
pthread_t *restrict thread
:- 类型:
pthread_t *
- 作用:用于保存创建线程的线程号。
- 注意:线程号不由用户指定,而是由系统分配。
- 类型:
- *const pthread_attr_t restrict attr:
- 类型:
const pthread_attr_t *
- 作用:表示创建线程的属性,如线程栈的大小、调度策略、优先级等。
- 常用值:
NULL
,表示使用默认属性。
- 类型:
- void *(start_routine)(void):
- 类型:
void *(*)(void*)
- 作用:表示创建线程成功后所执行的函数。
- 常用值:非
NULL
,指向线程函数的指针。
- 类型:
- *void restrict arg:
- 类型:
void *
- 作用:这个参数是传递给
start_routine
函数的参数。由于void*
类型的通用性,可以通过它传递任何类型的值,但通常需要将其强制转换为正确的类型。 - 常用值:
NULL
,表示不传递参数。
- 类型:
返回值
- 成功:返回
0
- 失败:返回非零值
线程退出与线程等待
线程退出
线程可以通过pthread_exit
函数来退出。函数原型如下:
void pthread_exit(void *value_ptr);
void* thread_function(void* arg) {
// 执行一些任务
printf("Thread is running...\n");
// ... 在这里执行更多任务 ...
// 使用pthread_exit退出线程,并返回一个值
pthread_exit((void*)42); // 返回值42作为示例
}
int main()
{
...
// 等待线程结束并获取返回值
result = pthread_join(thread_id, &thread_result);
...
}
- 参数:
value_ptr
:类型为void*
的指针,用于线程退出时返回一个值。如果不需要返回值,可以传递NULL
。
- 注意:
- 不可以返回自定义非静态局部变量的地址,因为这些变量在函数返回后就不存在了,可能会导致未定义行为。
线程等待
pthread_join
函数用于等待一个线程结束。函数原型如下:
int pthread_join(pthread_t thread, void **value_ptr);
int main()
{
...
// 等待线程结束并获取返回值
result = pthread_join(thread_id, &thread_result);
...
}
-
参数:
thread
:类型为pthread_t
,表示要等待的线程的线程号。value_ptr
:类型为void**
的指针,用于保存线程退出的返回值。如果不需要获取返回值,可以传递NULL
。
-
返回值:
- 成功:返回
0
- 失败:返回非零值
- 成功:返回
-
注意事项:
- 如果一个线程已经被其他线程等待过,或者它是分离的(通过
pthread_detach
设置),尝试再次对它调用pthread_join
将导致错误。 - 如果
value_ptr
参数是NULL
,那么子线程的退出状态将被忽略。 - 调用
pthread_join
会阻塞调用线程,直到指定的线程终止。
- 如果一个线程已经被其他线程等待过,或者它是分离的(通过
线程注册清理函数
在多线程编程中,可以在线程退出前注册清理函数,以确保在退出时执行某些清理操作。
注册清理函数
使用pthread_cleanup_push
函数来注册一个清理函数。
void pthread_cleanup_push(void (*routine)(void*), void *arg);
// 清理函数原型
void cleanup_routine(void *arg) {
// 打印传递给清理函数的参数
printf("%s\n", (char*)arg);
}
void* thread_function(void *arg) {
// 注册清理函数
pthread_cleanup_push(cleanup_routine, "Cleanup function called");
// 执行一些任务
printf("Thread is working...\n");
// 模拟线程需要执行清理的情况
// 在这里可以放置线程的主要工作代码
// 取消注册的清理函数,并且不执行它(因为第二个参数是0)
pthread_cleanup_pop(0);
// 返回一个值,表示线程的执行结果
return (void*)42;
}
- 参数:
routine
:类型为void (*)(void*)
的函数指针,表示清理时执行的函数。arg
:类型为void*
的指针,表示传递给清理函数的参数。
执行清理函数
使用pthread_cleanup_pop
函数来执行注册的清理函数。
void pthread_cleanup_pop(int execute);
- 参数:
- execute:一个整数,用于控制是否执行清理函数。
0
:表示不执行清理函数。- 非
0
:表示执行清理函数。
- execute:一个整数,用于控制是否执行清理函数。
注意事项
- 线程执行清理函数与代码放置的位置有关,且必须在注册清理函数之后的代码位置。
- 注册函数和执行函数必须一一对应。
- 注册函数的顺序和执行函数的顺序是相反的,即最后注册的清理函数会最先执行。
线程互斥锁
互斥锁(Mutex)是用于同步线程的一种机制,它确保同一时间只有一个线程可以访问共享资源。
动态锁
初始化锁
使用pthread_mutex_init
函数来初始化一个互斥锁。
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex;
int main() {
// 初始化互斥锁
int result = pthread_mutex_init(&mutex, NULL);
if (result != 0) {
perror("Mutex initialization failed");
return 1;
}
// 使用互斥锁进行线程同步的代码可以放在这里
// 销毁互斥锁
result = pthread_mutex_destroy(&mutex);
if (result != 0) {
perror("Mutex destruction failed");
return 1;
}
return 0;
}
- 参数:
mutex
:类型为pthread_mutex_t *
的指针,指向要初始化的互斥锁变量。attr
:类型为pthread_mutexattr_t *
的指针,指向互斥锁的属性。如果传递NULL
,则表示使用默认属性。
- 返回值:
- 成功:返回
0
- 失败:返回非零值
- 成功:返回
销毁锁
使用pthread_mutex_destroy
函数来销毁一个互斥锁。
int pthread_mutex_destroy(pthread_mutex_t *mutex);
- 参数:
mutex
:类型为pthread_mutex_t *
的指针,指向要销毁的互斥锁变量。
- 返回值:
- 成功:返回
0
- 失败:返回非零值
- 成功:返回
设置加锁和解锁
加锁
使用pthread_mutex_lock
函数来对互斥锁加锁。
int pthread_mutex_lock(pthread_mutex_t *mutex);
void* thread_function(void* arg) {
// 尝试对互斥锁加锁
int result = pthread_mutex_lock(&mutex);
if (result != 0) {
perror("Mutex lock failed");
return NULL;
}
// 互斥锁已加锁,可以安全地访问共享资源
printf("Thread %ld has the lock\n", (long)arg);
// 模拟执行一些任务
// 解锁互斥锁
result = pthread_mutex_unlock(&mutex);
if (result != 0) {
perror("Mutex unlock failed");
return NULL;
}
return NULL;
}
- 参数:
mutex
:类型为pthread_mutex_t *
的指针,指向要加锁的互斥锁变量。
- 返回值:
- 成功:返回
0
- 失败:返回非零值
- 成功:返回
尝试加锁
使用pthread_mutex_trylock
函数来尝试对互斥锁加锁。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
- 参数:
mutex
:类型为pthread_mutex_t *
的指针,指向要尝试加锁的互斥锁变量。
- 返回值:
- 成功:返回
0
- 失败:返回非零值
- 成功:返回
解锁
使用pthread_mutex_unlock
函数来对互斥锁解锁。
int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 参数:
mutex
:类型为pthread_mutex_t *
的指针,指向要解锁的互斥锁变量。
- 返回值:
- 成功:返回
0
- 失败:返回非零值
- 成功:返回
静态锁
可以使用宏PTHREAD_MUTEX_INITIALIZER
来静态初始化一个互斥锁。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;