Linux线程
Linux线程是操作系统内核中对线程的实现,它是轻量级的进程。Linux线程可以在多核处理器上并发执行,提高程序的运行效率。Linux线程支持多种线程同步机制,如互斥量,条件变量,读写锁等。
在Linux系统中,线程是通过clone系统调用实现的,这个系统调用可以复制当前进程的所有资源,并创建一个新的线程。新线程和原来的线程共享同一地址空间,但是有自己的线程上下文和线程ID。
通过pthread库
可以方便的使用Linux线程,这个库提供了很多函数来创建和管理线程,还有线程同步和线程间通信的机制等。
文章目录
进程与线程的区别
进程和线程都是操作系统中资源分配和调度的基本单位。
进程是资源分配和调度的最大单位,它拥有独立的地址空间和资源,如文件描述符和系统资源。每个进程都有自己的程序计数器,寄存器和栈空间。进程之间的资源独立,相互之间不会影响。
线程是资源调度的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位。线程有自己的程序计数器,寄存器和栈空间,但是它和同属于一个进程的其它线程共享进程的其它资源,如地址空间和全局变量。
因此,线程比进程更轻量级,创建和管理线程的代价比进程小,但是线程之间的资源共享会带来同步和通信上的问题。
pthread.h
pthread.h是pthread库的头文件,它定义了线程相关的类型和函数。这个库提供了在C语言中创建和管理线程的功能。
其中一些常用的函数有:
pthread_create()
:创建一个新线程
pthread_join()
:等待线程结束
pthread_exit()
:终止调用线程
pthread_cancel()
:取消一个线程
pthread_self()
:获取当前线程ID
pthread_equal()
:比较两个线程ID是否相同
pthread_mutex_lock()
:获取互斥锁
pthread_mutex_unlock()
:释放互斥锁
pthread_cond_wait()
:等待条件变量
pthread_cond_signal()
:唤醒等待条件变量的线程
使用pthread库创建线程比较简单,可以解决多线程编程中的问题。
函数原型及使用方法:
pthread_create
pthread_create
是pthread库
中用来创建新线程的函数。它的原型是:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
thread
: 指向pthread_t类型的指针,用于存储新线程的线程ID。
attr
: 指向pthread_attr_t类型的指针,用于设置线程的属性,如线程的分离属性等,如果为NULL,则使用默认属性
start_routine
: 指向线程入口函数的指针,该函数将在新线程中执行
arg
: 传递给线程入口函数的参数,它可以是任意类型的指针。
在调用pthread_create
函数后,操作系统会创建一个新线程,并在新线程中执行start_routine
函数,同时在主线程继续执行。
新线程和主线程是并发执行的。
pthread_create
函数返回0表示成功创建了新线程,返回非0的值表示创建线程失败。
例如:
void* thread_function(void* arg) {
//线程执行的代码
return NULL;
}
int main() {
pthread_t thread_id;
int ret = pthread_create(&thread_id, NULL, thread_function, NULL);
if (ret != 0) {
printf("Error creating thread\n");
return -1;
}
//主线程的代码
return 0;
}
pthread_join
pthread_join
是pthread库
中用来等待线程结束的函数。它的原型是:
int pthread_join(pthread_t thread, void **retval);
thread
: 要等待结束的线程ID
retval
: 用于存储线程结束时返回值的指针
调用pthread_join
函数会使调用线程阻塞,直到thread线程结束为止。如果thread线程已经结束,pthread_join
函数会立即返回。
如果我们需要等待所有线程结束后才继续执行主线程的代码,可以在创建线程后立即调用pthread_join
等待线程结束。
例如:
void* thread_function(void* arg) {
//线程执行的代码
return NULL;
}
int main() {
pthread_t thread_id;
int ret = pthread_create(&thread_id, NULL, thread_function, NULL);
if (ret != 0) {
printf("Error creating thread\n");
return -1;
}
ret = pthread_join(thread_id, NULL);
if (ret != 0) {
printf("Error joining thread\n");
return -1;
}
//主线程的代码
return 0;
}
上面代码中,主线程调用pthread_create函数创建了新线程,并调用pthread_join等待新线程结束。
返回0表示成功,返回非0值表示失败。
pthread_exit
pthread_exit
是pthread库
中用来终止线程并返回结果的函数。它的原型是:
void pthread_exit(void *retval);
retval
: 线程的返回值,可以是任意类型的指针
pthread_exit
函数用来终止调用它的线程并返回retval作为线程的结束状态。
当线程调用pthread_exit
函数时,它会立即终止,不会再执行后面的代码。如果有多个线程等待该线程结束,它们会立即返回。
如果线程没有显式调用pthread_exit
函数,那么在线程的入口函数执行完毕后,线程也会终止并返回。
例如:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void* thread_function(void* arg) {
int* result = (int*)malloc(sizeof(int)); // 分配内存
*result = 10;
pthread_exit(result); // 返回结果
}
int main() {
pthread_t thread_id;
int ret = pthread_create(&thread_id, NULL, thread_function, NULL);
if (ret != 0) {
printf("Error creating thread\n");
return -1;
}
void* thread_result;
ret = pthread_join(thread_id, &thread_result);
if (ret != 0) {
printf("Error joining thread\n");
return -1;
}
int* result = (int*) thread_result;
printf("Thread returned: %d\n", *result);
free(result); //释放内存
return 0;
}
在这个例子中,主线程调用pthread_create
创建了新线程并传入了thread_function
作为入口函数。在新线程中,thread_function
函数分配了一个int类型的内存并将其赋值为10,最后使用pthread_exit
函数返回该内存地址。
主线程在调用pthread_join
等待新线程结束,并将线程返回值存储在thread_result
中。最后,主线程打印返回值并释放内存。
这样做的好处是,我们可以在线程结束时获取其返回值,并在主线程中使用该值进行后续的操作。
另外,为了避免内存泄漏,在使用完返回值后,需要调用free
函数释放内存。
pthread_cancel
pthread_cancel
是pthread库
中用来取消线程的函数。它的原型是:
int pthread_cancel(pthread_t thread);
thread
: 要取消的线程ID
pthread_cancel
函数用来取消thread线程。取消线程后,线程的执行会立即终止,不会再执行后面的代码。如果有多个线程等待该线程结束,它们会立即返回。
需要注意,调用pthread_cancel并不会立即终止线程,而是在线程下一次取消点处终止线程。取消点是指线程执行到特定位置时会检查是否被取消的位置。
在Linux下,如果线程调用了cancel-safe函数(如pthread_cond_wait, pthread_join)将会检查取消信号,如果检测到信号就会终止线程。
pthread_cancel
函数返回0表示成功取消了线程,返回非0的值表示取消线程失败。
例如:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void* thread_function(void* arg) {
int i;
// 线程执行的代码
for (i = 0; i < 10; i++) {
printf("线程运行中: %d\n", i);
sleep(1); // 每隔一秒输出一次
}
return NULL;
}
int main() {
pthread_t thread_id;
int ret = pthread_create(&thread_id, NULL, thread_function, NULL);
if (ret != 0) {
printf("创建线程失败\n");
return -1;
}
sleep(5); // 主线程等待5秒
ret = pthread_cancel(thread_id);
if (ret != 0) {
printf("取消线程失败\n");
return -1;
}
ret = pthread_join(thread_id, NULL);
if (ret != 0) {
printf("等待线程结束失败\n");
return -1;
}
printf("线程已取消\n");
return 0;
}
上面的代码中,主线程调用pthread_create
函数创建了新线程,新线程执行thread_function
函数。在函数中输出"线程运行中: i",每隔1秒输出一次。主线程等待5秒后,调用pthread_cancel
取消新线程。最后调用pthread_join
等待新线程结束并打印"线程已取消"。
需要注意的是,在上面的例子中,由于新线程中没有调用取消点函数(如pthread_testcancel
),所以线程需要等待输出完所有的"线程运行中: i"后才会被取消。如果需要在调用pthread_cancel
后立即终止线程,可以在线程函数中调用pthread_testcancel
函数。
pthread_self
pthread_sel
f是pthread库
中用来获取当前线程ID的函数。它的原型是:
pthread_t pthread_self(void);
pthread_self
函数返回当前线程的线程ID,返回值类型是pthread_t
,是一个整数。
例如:
#include <pthread.h>
#include <stdio.h>
void* thread_function(void* arg) {
pthread_t thread_id = pthread_self();
printf("Thread ID: %lu\n", thread_id);
return NULL;
}
int main() {
pthread_t thread_id;
int ret = pthread_create(&thread_id, NULL, thread_function, NULL);
if (ret != 0) {
printf("Error creating thread\n");
return -1;
}
pthread_join(thread_id, NULL);
return 0;
}
上面的代码中,主线程调用pthread_create
函数创建了新线程,新线程执行thread_function
函数。在函数中调用pthread_self
函数获取当前线程的线程ID并打印出来。主线程调用pthread_join
等待新线程结束。
可以看到,pthread_self
函数可以在任意线程中调用,获取当前线程的线程ID。这在线程间通信中经常用到。
需要注意的是,pthread_self
返回的线程ID是一个整数,可能不是进程中的进程ID。进程ID和线程ID是不同的。
pthread_equal
pthread_equal
是pthread库
中用来比较两个线程ID是否相等的函数。它的原型是:
int pthread_equal(pthread_t t1, pthread_t t2);
t1
: 第一个线程ID
t2
: 第二个线程ID
pthread_equal
函数比较t1和t2两个线程ID是否相等,相等返回非0值,不相等返回0。
例如:
#include <pthread.h>
#include <stdio.h>
void* thread_function(void* arg) {
pthread_t thread_id = pthread_self();
printf("线程ID: %lu\n", thread_id);
return NULL;
}
int main() {
pthread_t thread1_id, thread2_id;
int ret = pthread_create(&thread1_id, NULL, thread_function, NULL);
if (ret != 0) {
printf("创建线程1失败\n");
return -1;
}
ret = pthread_create(&thread2_id, NULL, thread_function, NULL);
if (ret != 0) {
printf("创建线程2失败\n");
return -1;
}
int equal = pthread_equal(thread1_id, thread2_id);
if (equal != 0) {
printf("线程1和线程2相等\n");
} else {
printf("线程1和线程2不相等\n");
}
pthread_join(thread1_id, NULL);
pthread_join(thread2_id, NULL);
return 0;
}
上面的代码中,主线程调用pthread_create
函数创建了两个新线程,新线程执行thread_function
函数。在函数中调用pthread_self
函数获取当前线程的线程ID并打印出来。 主线程调用pthread_equal
函数判断两个新线程是否相等,并打印出结果。最后主线程调用pthread_join
等待两个新线程结束。
互斥锁与条件
与进程的信号量相似。这个操作存在与线程当中。
互斥锁的4个基本操作:
1、创建
2、销毁
3、加锁
4、解锁
条件的5个基本操作:
1、创建
2、销毁
3、发送
4、广播 这里就是发送的升级版可以给多个等待中的线程发送信号,这里不展开写了
5、等待
互斥锁与条件的初始化及释放(创建操作)
pthread_mutex_init(&mutex, NULL)
pthread_cond_init(&cond,NULL);
pthread_mutex_init()
函数 使用 attr(第二个参数) 指定的 属性 初始化 mutex 引用的 互斥量。如果 attr (第二个参数)为 NULL,则使用默认的互斥锁属性;效果与传递默认互斥属性对象的地址相同。成功初始化后,互斥锁的状态变为初始化和解锁。
pthread_cond_init()
函数 使用attr(第二个参数) 引用的 属性 初始化 cond引用的条件变量。如果 attr(第二个参数) 为 NULL,则使用默认的条件变量属性;效果与传递默认条件变量属性对象的地址相同。成功初始化后,条件变量的状态变为初始化状态。
与之相对的释放(销毁)
pthread_mutex_destroy(&mutex) //函数销毁 mutex 引用的 互斥 对象;互斥对象实际上变为未初始化。
pthread_cond_destroy(&cond) //销毁 cond指定的给定条件变量;该对象实际上变为未初始化。
初始化互斥锁及条件变量的宏
初始化互斥锁:PTHREAD_MUTEX_INITIALIZER
初始化条件变量:PTHREAD_COND_INITIALIZER
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_lock(加锁) 与 pthread_mutex_unlock(解锁)
pthread_mutex_lock
是pthread库
中用来获取互斥锁的函数。
pthread_mutex_unlock
是pthread库
中用来释放互斥锁的函数。它俩的原型是:
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);
mutex
: 指向互斥锁的指针
pthread_mutex_lock
函数尝试获取互斥锁,如果该互斥锁当前没有被其他线程锁定,则获取成功并返回0。如果该互斥锁当前被其他线程锁定,则该函数会阻塞等待直到该互斥锁被释放。
pthread_mutex_unlock
函数释放互斥锁,释放指定的互斥锁。
例如:
#include <pthread.h>
#include <stdio.h>
pthread_mutex_t mutex; // 互斥锁
int shared_var = 0; // 共享变量
void* thread_function(void* arg) {
int i;
for (i = 0; i < 1000000; i++) {
pthread_mutex_lock(&mutex); // 获取互斥锁
shared_var++; // 对共享变量进行操作
pthread_mutex_unlock(&mutex); // 释放互斥锁
}
return NULL;
}
int main() {
pthread_t thread1_id, thread2_id;
pthread_mutex_init(&mutex, NULL); // 初始化互斥锁
int ret = pthread_create(&thread1_id, NULL, thread_function, NULL);
if (ret != 0) {
printf("创建线程1失败\n");
return -1;
}
ret = pthread_create(&thread2_id, NULL, thread_function, NULL);
if (ret != 0) {
printf("创建线程2失败\n");
return -1;
}
pthread_join(thread1_id, NULL);
pthread_join(thread2_id, NULL);
printf("最终的共享变量值: %d\n", shared_var);
pthread_mutex_destroy(&mutex); // 销毁互斥锁
return 0;
}
上面的代码中,主线程创建了两个新线程,新线程执行thread_function
函数。在函数中,先使用pthread_mutex_lock
获取互斥锁,然后对共享变量进行操作,最后使用pthread_mutex_unlock
释放互斥锁,期间如果有其他线程都将阻塞直到释放互斥锁。
pthread_cond_signal 与 pthread_cond_wait(发送与等待)
pthread_cond_signal
是pthread库
中用来发送条件的函数。
pthread_cond_wait
是pthread库
中用来等待条件的函数。它俩的原型是:
pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
cond
条件变量
mutex
互斥锁
需要注意的是:
pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
这个函数会先解锁 mutex ,然后等待cond条件变量,当其他线程调用pthread_cond_signal或者pthread_cond_broadcast唤醒等待的线程,然后再加锁 mutex.
下面是一个实例代码用来理解互斥锁与条件:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
/* 初始化互斥锁和条件变量 */
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int flag = 0;
void *thread1(void *arg)
{
int ret;
/* 加锁 */
ret = pthread_mutex_lock(&mutex);
if (ret != 0) {
/* 错误处理 */
perror("pthread_mutex_lock");
pthread_exit(NULL);
}
printf("线程1:加锁成功\n");
flag = 1;
/* 发送信号 */
pthread_cond_signal(&cond);
printf("线程1:发送信号\n");
/* 解锁 */
ret = pthread_mutex_unlock(&mutex);
if (ret != 0) {
/* 错误处理 */
perror("pthread_mutex_unlock");
pthread_exit(NULL);
}
printf("线程1:解锁成功\n");
pthread_exit(NULL);
}
void *thread2(void *arg)
{
int ret;
/* 加锁 */
ret = pthread_mutex_lock(&mutex);
if (ret != 0) {
/* 错误处理 */
perror("pthread_mutex_lock");
pthread_exit(NULL);
}
printf("线程2:加锁成功\n");
/* 等待信号 */
while(!flag) {
printf("线程2:等待信号\n");
pthread_cond_wait(&cond, &mutex);
}
printf("线程2:收到信号\n");
/* 解锁 */
ret = pthread_mutex_unlock(&mutex);
if (ret != 0) {
/* 错误处理 */
perror("pthread_mutex_unlock");
pthread_exit(NULL);
}
printf("线程2:解锁成功\n");
pthread_exit(NULL);
}
int main()
{
int ret;
pthread_t t1, t2;
/* 创建线程 */
ret = pthread_create(&t1, NULL, thread1, NULL);
if (ret != 0) {
/* 错误处理 */
perror("pthread_create thread1");
return -1;
}
ret = pthread_create(&t2, NULL, thread2, NULL);
if (ret != 0) {
/* 错误处理 */
perror("pthread_create thread2");
return -1;
}
/* 等待线程结束 */
pthread_join(t1, NULL);
pthread_join(t2, NULL);
return 0;
}
在这个例子中, thread1 会在第一次运行时首先获得互斥锁,并将 flag 设置为 1。 然后它会发出一个信号,告诉 thread2 条件已经满足,可以继续运行了。 thread2 会一直等待,直到条件变量被信号触发。
结束
爆肝学习过程,对你有帮助的话,点个赞支持一下吧。
转载请联系本人。