找回密码
 立即注册
  • QQ空间
  • 回复
  • 收藏

跟我学c++初级篇——多线程实践之三Linux平台创建

admin 2019-2-11 10:14 336人围观 C++相关

作者:太平洋工作室



一、Linux下的多线程


在Linux操作系统中,原来是没有多线程的概念的,多线程的体系的建立是有一个过程的。早期的Linux中只存在多进程编程,使用fork来创建不同的进程来达到并发的目的。在后期延续了两条线路,即使用LinuxThreads来模拟多线程和使用Posix中的NPTL。

二、Linux上的多线程使用


在Linux上创建多线程主要有以下几个API函数:

1、创建多线程


int pthread_create(pthread_t *thread,const pthread_attr_t* attr,void*(*start routine)(void*),void *arg)
thread:新线程的ID
attr:attr参数用于设置各种不同的线程属性,一般为NULL表示使用默认的线程属性(比如下面的分离属性可以在此处设置)
start routine:线程函数的地址
arg:线程传参指针
成功返回0,错误返回相对的编号。
简要说明一下属性attr,更详细的请查看相关资料:
detachstate:线程的分离状态。即PTHREAD_CREATE_JOINABLE和PTHREAD_CREATE_DETACH.前者指定为线程可回收,后者为线程分离。默认为前者。
stackaddr,stacksize:线程堆栈的起始地址和大小。这个一般不用管理。默认一般是10M。
schedparam:线程调度参数,目前只可以设置优先级。
schedpolicy:线程调度策略。
scope:多线程间竞争CPU的范围,目前只有两种,一种是所有线程一起竞争,另外一种是只有同一进程间的线程竞争。

2、退出线程


退出线程一般有三种方法:
1)让线程自然退出。即线程安全执行完成,从启动函数中实例中返回,返回值即为退出码。这是推荐的方式。
2)取线程,使用pthread_cancle来取消线程的执行。这具有一定的危险性,特别是初学者。如果考虑不周,可能造成资源或者内存的泄露。
3)使用pthread_exit函数自己主动退出。

a)取消线程
int pthread_cancel(pthread_t thread);
thread:取消线程的ID。
成功返回值为0,错误返回错误编号
它实际调用了设置参数为PTHREAD_CANCEL的pthread_exit函数,不过接收到请求的目标线程可以决定是滞允许被取消以及如何取消。它由下面两个函数来控制:
b)设置线程对Cancel信号的反应
int pthread_setcancelstate(int state, int *oldstate);
state:PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE,即接收到信号后允许CANCEL和忽略此信号。
old_state:不为NULL则存入原来的Cancel状态以便恢复。
c)设置本线程取消动作的执行时机
int pthread_setcanceltype(int type, int *oldstate);
type:PTHREAD_CANCEL_DEFFERED和PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和立即执行取消动作(退出)
oldtype:不为NULL则存入运来的取消动作类型值。
d) 离开线程
void pthread_exit(void *rval_ptr)
rval_ptr:向指定接收者(调用线程的回收者)传递退出信息。使用pthread_join函数可以访问到这个指针。
需要注意的是,本函数执行完成后不会返回到调用者,且本函数调用永远成功。

3、加入线程组(等待线程结束)


int pthread_join(pthread_t thread, void **rval_ptr);
thread:线程id
rval_ptr:线程返回时的退出信息
本函数一直会阻塞,直到被回收的线程结束为止。成功返回0,错误返回:
EDEADLK:可能引起死锁,比如两个线程互相调用pthread_join或者自己内部调用pthread_join
EINVAL:目标线程不可回收,或者已被其它线程回收
ESRCH:目标线程不存在

4、线程的分离


如果创建的线程没有什么必要和主线程发生关系亦或者其它原因,可以使用pthread_detach分离线程
int pthread_detach(pthread_t tid)
tid:线程ID。
成功能加零,失败返回:
EINVAL:已分离
ESRCH:tid不是当前进程中有效的分离线程。

三、实例


分析了上面的多线程相关的API函数后,看下面的例子:
创建一个简单的多线程并等待执行完成
1#include <iostream>
2#include <unistd.h>
3#include <pthread.h>
4
5void * run(void *arg)
6{
7    int num = 0;
8    pthread_t id = *(pthread_t*)arg;
9
10    while (num++ < 3)
11    {
12        sleep(1);
13        std::cout<<"sub thread:"<<std::endl;
14        std::cout<<"pid is:"<<getpid()<<",ppid is:"<<getppid()<<",t_id is:"<<pthread_self()<<std::endl;
15    }
16}
17void * d_run(void *arg)
18{
19    std::cout<<"detach thread run"<<std::endl;
20    pthread_exit(0);
21}
22
23int main()
24{
25    std::cout<<"pthread_create create thread"<<std::endl;
26
27    pthread_t t_id;
28    pthread_t master_id = pthread_self();
29
30
31    pthread_t dt_id;
32    int dret = pthread_create(&dt_id,NULL,d_run,NULL);
33
34    int ret = pthread_create(&t_id,NULL,run,&master_id);
35
36    if (ret != 0 || dret != 0)
37    {
38        std::cout<<"create thread  error"<<std::endl;
39        return -1;
40    }
41    else
42    {
43        sleep(1);
44        std::cout<<"master thread:"<<std::endl;
45        std::cout<<"pid is:"<<getpid()<<",ppid is:"<<getppid()<<",t_id is:"<<pthread_self()<<std::endl;
46    }
47
48    pthread_detach(dt_id);
49
50    int code;
51    pthread_join(t_id,(void**)&code);
52    std::cout<<"master thread is end:"<<code<<std::endl;
53
54    return 0;
55}

程序的运行结果:



程序的运行看上去没有问题,但是实际上会有问题么?或者说这样写,会不会在某些情况下出现Crash的情况呢?换一个写法来试一下。把上面的代码略微修改一下:

1void * run(void *arg)
2{
3    int num = 0;
4    pthread_t id = *(pthread_t*)arg;
5
6    while (num++ < 1)
7    {
8        sleep(1);
9        std::cout<<"sub thread:"<<std::endl;
10        std::cout<<"pid is:"<<getpid()<<",ppid is:"<<getppid()<<",t_id is:"<<pthread_self()<<std::endl;
11    }
12}
13void * d_run(void *arg)
14{
15    sleep(3);
16    std::cout<<"detach thread run"<<std::endl;
17    pthread_exit(0);
18}

重新编译,运行结果如下:



thread1

可能会说,这没有崩溃啊。程序运行的挺好啊,可是仔细观察会发现,deatch的线程并没有运行,这意味着什么?意味着如果你在多线程操作中操作了主线程内分配的资源程序就极有可能崩溃。举一个比较极端的例子,在主线程中分配了一块内存,然后在两个线程中分别使用并释放。程序就会有大概率会崩溃。
重点来了:不同的LINUX的版本和系统,对上面代码的处理有所不同,可能导致崩溃的可能性不同,一定注意。

四、总结


在编译Linux多线程支持时,经常可以看到两种形式,即使用 -lpthread 和 -pthread。使用的时候没有觉出它们有什么不同啊。单纯从形式来看,如果仅仅是从上面的小例程来考虑,确实是没有什么区别。但是在gcc的编译手册介绍中,对二者进行了说明:
在旧版本中,或者在一些比较老的书里,一般都是推荐使用-lpthread,而在GCC手册中指出应该在编译和链接时都都加-pthread选项。
编译选项中指定 -pthread 会附加一个宏定义 -D_REENTRANT,该宏会导致 libc 头文件选择那些thread-safe的实现;链接选项中指定 -pthread 则同 -lpthread 一样,只表示链接 POSIX thread 库。由于 libc 用于适应 thread-safe 的宏定义可能变化,因此在编译和链接时都使用 -pthread 选项而不是传统的 -lpthread 能够保持向后兼容,并提高命令行的一致性。
在高版本的GCC4.5.2以上,已经只对-pthread编译进行有效说明,由此可推出,目前推荐优先使用-pthread选项。
多线程编程是一个比较复杂的过程,任何的马虎大意,都可能造成整个线程的运行的鲁棒性,一定要谨慎处理线程的各种情况。




-------------------------------------------------------------------------
我们尊重原创,也注重分享,如若侵权请联系qter@qter.org。
-------------------------------------------------------------------------

鲜花

握手

雷人

路过

鸡蛋

yafeilinux和他的朋友们微信公众号二维码

微信公众号

专注于Qt嵌入式Linux开发等。扫一扫立即关注。

Qt开源社区官方QQ群二维码

QQ交流群

欢迎加入QQ群大家庭,一起讨论学习!

我有话说......