linux c线程(1)-基础

2018-06-13 10:50:07

与进程类似,线程是允许应用程序并发执行多个任务的一种机制。一个进程可以包含多个线程。线程间共享同一份全局内存区域,其中包括初始化数据段,未初始化数据段,堆内存。

传统unix上,进程其实多线程的一个特例,该进程包含一个线程(主线程)。

Pthreads

pthreads api是posix对线程的标准化api。部分数据结构如下。

数据类型 描述 
 pthread_t 线程id
 pthread_mutex_t 互斥对象
 pthread_mutexattr_t 互斥属性对象
 pthread_cond_t 条件变量
 phtread_condattr_t 条件变量的属性对象
 pthread_key_t 线程特有数据的键
 pthread_once_t 一次性初始化控制上下文
 pthread_attr_t 线程的属性对象

每个线程都有其自己的errno,errno为一个宏,展开是一个函数,返回是左值,所以可以以errno=value方式赋值,因为是一个函数,所以每次赋值都是函数调用,略耗性能。

pthreads 函数0表示成功,返回正值表示失败,编译时带上 -lpthread。

创建线程

启动程序时,产生的进程只有单条线程,称之为初始或主线程。函数pthread_create() 负责创建一条新线程。

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start)(void *), void *arg);

//Returns 0 on success, or a positive error number on error


终止线程

终止线程有如下几种方式:

1.线程函数执行return返回。
2.线程调用pthread_exit()。
3.调用pthread_cancel()取消线程。
4.任意线程调用了exit(),或者主线程执行了return语句(在main函数中),都会导致进程中的所有线程立即终止。
5.如果在任一线程内部调用exec函数,那么所有线程将会立即消失。

pthread_exit() 函数将终止调用线程,且其返回值可由另一线程调用pthread_join()来获取。

include <pthread.h>

void pthread_exit(void *retval);

调用pthread_exit()相当于在线程start 函数中执行return,不同之处在于可在线程start函数所调用的任意函数中调用pthread_exit()。

参数retval指定了线程的返回值,注意retval所指向的内容不应分配与栈中。

线程ID

进程内部的每个线程都有一个唯一标识,称为线程ID。线程ID会返回给pthread_create()的调用者,一个线程可以通过pthread_self()来获取自己的线程ID。

include <pthread.h>

pthread_t pthread_self(void);

//Returns the thread ID of the calling thread

不同线程通过线程id来标识要操作的目标线程。这些函数包含pthread_join()、pthread_detach()、pthread_cancel()和pthread_kill()等。

include <pthread.h>

int pthread_equal(pthread_t t1, pthread_t t2);

//Returns nonzero value if t1 and t2 are equal, otherwise 0

pthread_equal() 用来判断2个线程id是否相同,在Linux环境中pthread_t为无符号长整型,其他不一定,也可能是结构体。如判断tid和本线程id是否一致。

if (pthread_equal(tid, pthread_self())
    printf("tid matches self\n");

在 Linux的线程实现中,线程ID在所有进程中都是唯一的,在其他实现中不一定。


连接(join)以终止的线程

函数pthread_join()等待由thread标识的线程终止。如果线程已经终止,立即返回。

include <pthread.h>

int pthread_join(pthread_t thread, void **retval);

//Returns 0 on success, or a positive error number on error

若 retval 为一非空指针,将会保存线程终止时返回值的拷贝,亦即调用return 或 pthread_exit()时所指定的值。

若线程并未分离(detached,下面讲解),则必须使用pthread_join()来进行连接,如果未能连接,那么将产生僵尸线程,类似于僵尸进程,会造成资源浪费。

线程之间是对等的,比如线程A创建了线程B,线程B创建了线程C,那么线程C也可以连接线程A。线程通过线程ID必须明确指定要连接的是哪个线程,不像进程可以做到等待任意子进程。线程也不能以非阻塞进行连接。

例子

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

#include <pthread.h>

static void * threadFunc(void *arg){
    char *s = (char *) arg;
    printf("%s", s);
    return (void *) strlen(s);
}

int main(int argc, char *argv[]){
    pthread_t t1;
    void *res;
    int s;

    s = pthread_create(&t1, NULL, threadFunc, "Hello world\n");
    if (s != 0)
        perror("pthread_create");

    printf("Message from main()\n");
    s = pthread_join(t1, &res);
    if (s != 0)
        perror("pthread_join");

    printf("Thread returned %ld\n", (long) res);

    exit(EXIT_SUCCESS);
}
[root@izj6cfw9yi1iqoik31tqbgz c]# gcc main.c -lpthread
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out 
Message from main()
Hello world
Thread returned 12


线程的分离

在默认情况下,线程必须经过pthread_join()来获取线程的终止状态并释放线程资源,当我们不关心线程返回状态,只是希望线程终止时自动清理并移除,那么可以调用pthread_detach()。

#include <pthread.h>

int pthread_detach(pthread_t thread);

//Returns 0 on success, or a positive error number on error

线程属性

前面提到过pthread_attr_t 的attr 参数,可利用其在创建线程时指定新线程的属性。在这里不深入,有兴趣的朋友可以参考相关文档。


线程 VS 进程

线程的优点
1.线程间共享数据很简单,而进程需要用到一些额外技术比如比如共享内存。
2.创建线程速度要比创建进程快10倍左右。线程间上下文切换消耗的时间一般也比进程间短。

线程的缺点
1.多线程编程时,需要确保调用线程安全函数,或者以线程安全的方式来调用函数。
2.某个线程的Bug可能会危及该进程中的所有线程,因为它们共享者相同的地址空间和其他属性。,而进程隔离的更彻底。
3.每个线程都在争用宿主进程中有限的虚拟地址空间。当进程分配大量的线程或线程使用了大量的内存时,则后续线程将无缘使用这些区域。而进程可以使用全部的有效虚拟内存,仅受限于实际内存和交换空间。


总结

在项目开发中到底选择多线程还是多进程要根据实际情况来做决定,在超高并发的情况下,一般这两种都不适合,后面讲到的事件驱动epoll会更有优势,比如现代的web服务器nginx就是采用事件驱动来处理高并发,如果有疑问可以给我留言。


备注
1.编译器版本gcc4.8,运行环境centos7 64位
2.原文地址http://www.freecls.com/a/2712/54

©著作权归作者所有
收藏
推荐阅读
  • err
    linux c程序的执行

    执行新程序:execve系统调用 execve() 可以将新程序加载到某一进程的内存空间。进程的栈、数据段、堆都会被新程序的相应部件所替换。由fork() 生成的子进程对...

  • err
    linux c监控子进程

    接下来描述两种监控子进程的技术:系统调用wait()及其变体,SIGCHLD信号。系统调用wait()#include &lt;sys/wait.h&gt; pid_t...

  • err
    linux c进程的创建、终止

    对进程不是很了解的同学可以参考linux 进程这篇文章。进程的创建系统调用fork() 创建一新进程(子进程)。#include &lt;unistd.h&gt; pi...

  • linux c定时器与休眠

    定时器是进程规划自己在未来某一时刻接获通知的一种机制。休眠则能是进程(或线程)暂停执行一段时间。间隔定时器系统调用 setitimer() 创建一个间隔式定时器,一定时间后到期,这个定时器也可以每个一...

  • linux c信号(3)-高级特性

    core dump文件coredump 文件 内含进程终止时内存映像的一个文件,把它加载到调试器中,即可查明信号到达时程序代码和数据的状态。 引发core dump文件除了程序中调用 abort()外...

  • nginx模块 ngx_http_headers_module

    ngx_http_headers_module 模块是用来增加 Expires 和 Cache-control,或者是任意的响应头。Syntax: add_header name value [alw...

  • nginx模块 ngx_http_gunzip_module、ngx_http_gzip_module、ngx_http_gzip_static_module

    ngx_http_gunzip_module 模块将文件解压缩后并在响应头加上 "Content-Encoding: gzip" 返回给客户端。为了解决客户端不支持gzip压缩。编译的时候带上 --w...

  • nginx模块 ngx_http_flv_module、ngx_http_mp4_module

    ngx_http_flv_module模块提供了对 flv 视频的伪流支持。编译的时候带上 --with-http_flv_module。它会根据指定的 start 参数来指定跳过多少字节,并在返回数...

  • nginx模块 ngx_http_fastcgi_module

    ngx_http_fastcgi_module 模块使得nginx可以与 fastcgi 服务器通信。比如目前要使得 nginx 支持 php 就得使用 fastcgi技术,在服务器上装上 nginx...

  • nginx模块 ngx_http_autoindex_module

    ngx_http_autoindex_module 模块可以将uri以 / 结尾时,列出里面的文件和目录。Syntax: autoindex on | off; Default: autoindex ...

简介
天降大任于斯人也,必先苦其心志。