下面详细讲解Linux线程的创建方式。
创建线程的方式
在Linux中,我们可以通过pthread
库来创建线程,其中比较常用的三种方式分别是:
- 使用
pthread_create
函数来创建线程。 - 使用
fork
函数创建进程,然后使用pthread_create
函数在新进程中创建线程。 - 使用
clone
系统调用来创建线程。
下面分别对这三种方式进行详细说明。
使用pthread_create
函数创建线程
pthread_create
函数的声明如下:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
其中,thread
参数是一个指向pthread_t
类型变量的指针,用于存储新创建线程的ID。attr
参数用于指定线程的属性,一般使用默认属性即可,传入NULL
即可。start_routine
参数是线程的入口函数,arg
参数是传给线程函数的参数,如果不需要传递参数,也可以传入NULL
。
下面是一个简单的例子,创建一个新线程来输出一个字符串:
#include <pthread.h>
#include <stdio.h>
void *my_thread_func(void *arg)
{
char *str = (char *)arg; // 将参数转换为字符串指针类型
printf("%s\n", str);
return NULL;
}
int main()
{
pthread_t tid;
char *str = "Hello, world!";
// 创建一个新线程,执行my_thread_func函数,传入参数str
pthread_create(&tid, NULL, my_thread_func, (void *)str);
// 等待新线程结束
pthread_join(tid, NULL);
return 0;
}
在上面的例子中,我们使用pthread_create
函数创建了一个新线程,并将其ID存储在了tid
变量中。新线程执行的函数是my_thread_func
,它的参数是一个字符串指针,指向字符串"Hello, world!"
。在my_thread_func
函数中,我们将传入的字符串打印出来。最后,使用pthread_join
函数等待新线程结束。
使用fork
函数和pthread_create
函数创建线程
我们也可以使用fork
函数创建子进程,然后在子进程中使用pthread_create
函数创建线程。这种方式的优点是,可以避免线程之间的竞争问题。
下面是一个简单的例子,创建一个子进程,在子进程中创建一个新线程:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void *my_thread_func(void *arg)
{
char *str = (char *)arg; // 将参数转换为字符串指针类型
printf("%s\n", str);
return NULL;
}
int main()
{
pid_t pid;
pthread_t tid;
char *str = "Hello, world!";
// 创建子进程
if ((pid = fork()) < 0) {
perror("fork error");
exit(1);
}
// 在子进程中创建新线程
else if (pid == 0) {
pthread_create(&tid, NULL, my_thread_func, (void *)str);
pthread_join(tid, NULL);
}
return 0;
}
在上面的例子中,我们首先使用fork
函数创建了一个子进程。在子进程中,我们再使用pthread_create
函数创建了一个新线程,并将其ID存储在了tid
变量中。然后,等待新线程结束。需要注意的是,这种方式创建的线程与主线程并不共享相同的地址空间,因此需要使用fork
函数创建子进程。
使用clone
系统调用创建线程
clone
系统调用是Linux中创建线程的底层方法。下面是clone
函数的定义:
#include <sched.h>
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...
/* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );
其中,fn
参数是线程的入口函数。child_stack
参数指定新线程的栈空间,可以传入NULL
,这样系统会自动为新线程分配栈空间。flags
参数是标志位,用于指定新线程与父进程之间共享的资源,比如文件描述符、信号处理器等等。如果不需要共享资源,可以传入CLONE_VM
标志位。arg
参数是传给线程函数的参数,如果不需要传递参数,也可以传入NULL
。
下面是一个简单的例子,使用clone
函数创建一个新线程:
#define _GNU_SOURCE // 在使用clone前宏定义该宏
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];
void *my_thread_func(void *arg)
{
char *str = (char *)arg; // 将参数转换为字符串指针类型
printf("%s\n", str);
return NULL;
}
int main()
{
pid_t pid;
char *str = "Hello, world!";
// 创建一个新线程
pid = clone(my_thread_func, child_stack + STACK_SIZE, CLONE_VM | SIGCHLD, (void *)str);
if (pid < 0) {
perror("clone error");
exit(1);
}
// 等待新线程结束
waitpid(pid, NULL, 0);
return 0;
}
在上面的例子中,我们首先宏定义了_GNU_SOURCE
,这个宏是为了指定使用GNU的一些扩展函数,其中包括clone
函数。然后,在main
函数中,我们使用clone
函数创建一个新线程,并将线程函数的地址作为第一个参数传入。新线程共享父进程的地址空间,因此传入CLONE_VM
标志位。新线程的栈空间是child_stack
数组的末尾,参数flags
指定了在新线程创建后向父进程发送信号SIGCHLD
。最后,使用waitpid
函数等待新线程结束。
结束语
以上就是在Linux中创建线程的三种方式。其中,使用pthread_create
函数是最常用的方法,也是最方便的方法。使用fork
函数和pthread_create
函数创建线程可以有效避免线程之间的竞争问题,但会导致代码复杂度提高。使用clone
函数则是底层的创建线程方法,可以更加细粒度地控制新线程的行为。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Linux之线程的创建方式 - Python技术站