关闭

线程初步(1)—— 线程的创建、参数和返回值、结束、状态、取消

标签: 线程
812人阅读 评论(0) 收藏 举报
分类:

1线程(thread)的概念和特点

  网络一般都需要实现代码的并行。代码的并行必须借助多进程/多线程。
  主流操作系统中都是支持多进程,而在每个进程的内部,都支持多线程并行。

  进程,重量级的,拥有自己独立的内存空间。
  线程,轻量级的,不需要拥有自己独立的内存空间,只是额外拥有一个独立的栈。一个进程内存的所有线程共享进程的资源(代码区、全局区、堆、文件目录……)。
  进程中支持多线程并行,其中有一个是主线程,进程中必须有主线程(main函数)。

  因此网络开发经常是网络+多线程模式。


2 线程的实现原理

  计算机程序运行的硬件必备:CPU、内存。如果要并行,意味着CPU和内存都应该可分。内存是可分的,但CPU不可分,那么多线程怎么并行?
  主流操作系统采用CPU时间片技术实现多线程的并行。人的感知是需要时间的,这种时间属于时间段,比如0.1秒,对于计算机来说,0.1秒可以分为100毫秒。把100毫秒的CPU执行时间分成100个CPU时间片,每个 1毫秒。假如有4个线程并行,每个线程分1片,4毫秒以后,每个线程都运行了1毫秒。针对时间点来说,线程没有并行;针对时间段来说,利用CPU时间片技术可以实现并行。

  多线程之间互相独立,但又互相影响。
  主线程一旦结束,进程随之结束,进程结束导致所有线程结束。
  多线程之间代码是乱序执行,每个线程内部的代码是顺序执行。


3 线程的实现

  POSIX规范中对线程做了比较完善的定义,因此,线程编码使用 pthread.h,几乎所有的函数都以pthread_ 开头。代码在libpthread.so中。

  比如:创建线程的函数:
  pthread_create()
  4个指针类型做参数:
    第一个参数:用于存储pthread_t 类型的线程ID
    第二个参数: 线程属性,一般为0即可(默认属性)
    第三个参数和第四个参数联合使用,第三个参数是函数指针,把线程需要执行的代码写在函数中,函数的参数由第四个参数提供。

void* (*fun) (void*)

  返回,成功返回0,失败返回错误码。线程的函数错误处理通过返回错误码的方式,而不是使用errno。

#include <stdio.h>
#include <pthread.h>
#include <string.h>

void* task(void* p){
    int i;
    for(i=0;i<100;i++){
        printf("task:%d\n",i); 
    } 
}

int main(){
    pthread_t id;//用来存储线程ID
    printf("size=%d\n",sizeof(id));
    int res = pthread_create(&id,0,task,0);
    if(res/*!=0*/) 
        printf("create error:%s\n",strerror(res));//线程错误处理
    int i; 
    for(i=0;i<100;i++){
        printf("main:%d\n",i);
    }
    sleep(1); 
}

4 线程的参数和返回值

4.1 线程的参数

  在使用线程的参数时,必须保证参数的指向有效。
  pthread_join()可以让一个线程等待另外一个线程结束,并且取得结束线程的返回值。(类似wait)

#include <stdio.h>
#include <pthread.h>

void* task(void* p){//p就是create()第4个参数
    int* pi = p;
    printf("*pi=%d\n",*pi);
    *pi = 200;
}

//练习:线程传入圆的半径,打印圆的面积
void* task2(void* p){
    double* pd = p;
    printf("s=%lf\n",3.14*(*pd)*(*pd)); 
}

int main(){
    pthread_t id1,id2,id3;
    int x = 100;
    pthread_create(&id1,0,task,&x);
    id2 = pthread_self();//取当前线程的ID
    printf("id1=%u,main=%u\n",id1,id2);
    pthread_join(id1,0); 
    printf("x=%d\n",x);
    double d = 1.0;
    pthread_create(&id3,0,task2,&d);
    pthread_join(id3,0);
}
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

void* task(void* p){
    int x = (int)p;//可以拿指针当int用
    printf("x=%d\n",x);  
}

void* task2(void* p){
    sleep(1); 
    int* pi = p;//p已经被释放,无效
    printf("*pi=%d\n",*pi);
}

int main(){
    pthread_t id1,id2;
    int x = 100;
    pthread_create(&id1,0,task,(void*)x);//传int
    pthread_join(id1,0);
    int* pi = malloc(4); *pi = 100;
    pthread_create(&id2,0,task2,pi);
    free(pi); pthread_join(id2,0);
}

4.2 线程的返回值

关于函数/线程的返回值:
  1 不能直接以数组做返回类型;
  2 能返回局部变量,但不能返回指向局部变量的指针;
  3 加了static的变量指针可以返回。

  线程的返回值必须是一个有效的指针:全局变量、常量、传入的指针、static的局部变量。
  线程的返回值可以pthread_join的第二个参数取得:
    pthread_join(pthread_t id,void** retval)
  取返回值时,相当于代码:
    *(retval) = 线程的返回值

#include <stdio.h>
#include <pthread.h>
#include <string.h>

void* task(void* p){
    printf("%s\n",(char*)p);
    //p = "hello";//改地址,p指向只读常量区
    strcpy(p,"hello");//没有改地址main()有效
    //char st[] = "hello";//局部变量,返回无效
    //return st;
    return p;//res = p;
}

//练习:在线程中计算1-10的和,并返回给main
void* task2(void* p){
    /*static*/ int sum = 0;
    int i;
    for(i=1;i<11;i++){
        sum = sum+i;
    }
    return (void*)sum;//&sum; 
}

int main(){
    char str[] = "abcde"; pthread_t id;
    pthread_create(&id,0,task,str);
    char* res;//res是 野指针
    pthread_join(id,(void**)&res);//res = p;
    printf("res=%s\n",res);
    pthread_create(&id,0,task2,0);
    //int* pi;
    //pthread_join(id,(void**)&pi);
    //printf("*pi=%d\n",*pi);
    int x;
    pthread_join(id,(void**)&x);
    printf("x=%d\n",x);
}

5 线程的结束

正常结束:
  线程函数结束
  pthread_exit(void* retval),与return一样
非正常结束:
  出错/被其他线程取消
注:exit()结束的是进程,所以不能用于结束线程。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void* task(void* p){
    int i;
    for(i=0;i<100;i++){
    //if(i == 12)  return (void*)i;
    //if(i == 12) pthread_exit((void*)i);
    if(i == 12) exit(i);//结束进程
    } 
}

int main(){
    pthread_t id;
    pthread_create(&id,0,task,0);
    int res;
    pthread_join(id,(void**)&res);
    printf("res=%d\n",res);
}

6 线程的状态

线程在启动后,可以通过不同的函数进入不同的状态:
  pthread_join() 进入非分离状态(同步),非分离状态的线程会在pthread_join()结束后回收线程资源。
  pthread_detach() 进入分离状态(异步),分离状态的线程会在线程结束后直接回收线程的资源。
  已经处于分离状态的线程 join()没有效果。线程最好处于这两种状态其中的一种。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* task(void* p){
    int i;
    for(i=0;i<10;i++){
        printf("task:%d\n",i); usleep(100000);
    }
}

int main(){
    pthread_t id;
    pthread_create(&id,0,task,0);
    pthread_detach(id);
    pthread_join(id,0);
    int i;
    for(i=0;i<10;i++){
        printf("main:%d\n",i); usleep(100000);
    }
}

7 线程的取消(了解)

  线程的取消就是给目标线程发CANCEL信号,目标线程可以做出3种选择:忽略、立刻停止、过一会再停止。
  线程取消的相关函数:
    pthread_cancel() 给目标线程发取消信号
    pthread_setcancelstate() 设置是否支持取消
    pthread_setcanceltype() 设置取消的方式

#include <stdio.h>
#include <pthread.h>

void* task1(void* p){
    //pthread_setcancelstate(//不能取消
    //PTHREAD_CANCEL_DISABLE,0);
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,0);
    //pthread_setcanceltype(//立即取消
        //PTHREAD_CANCEL_ASYNCHRONOUS,0);
    pthread_setcanceltype(//到下一个取消点
        PTHREAD_CANCEL_DEFERRED,0);
    while(1) 
        printf("-----------\n"),usleep(1);
}

void* task2(void* p){
    sleep(3);
    printf("取消线程1\n");
    pthread_cancel(*(pthread_t*)p);
}

int main(){
    pthread_t id1,id2;
    pthread_create(&id1,0,task1,0);
    pthread_create(&id2,0,task2,&id1);
    pthread_join(id1,0); pthread_join(id2,0);
}
1
0
查看评论

线程终止——线程的返回值

线程终止   如果进程中的任一线程调用了exit,_Exit或者_exit,那么整个进程就会终止。与此类似,如果信号的默认动作是终止进程,那么,把该信号发送到线程会终止整个进程。 单个线程可以通过下列三种方式退出,在不终止整个进程的情况下停止它的控制流。 (1):从...
  • yhj110911119
  • yhj110911119
  • 2016-08-20 23:28
  • 880

linux c 线程的创建与结束 (传参和返回值)

1. 线程的创建
  • SwineX00
  • SwineX00
  • 2014-08-15 16:35
  • 4599

MFC 判断线程是否结束

函数: GetExitCodeThread() 功能: 获取一个结束线程的返回值 函数原形: BOOL GetExitCodeThread( HANDLE hThread, LPDWORD lpExitCode); 参数: hThr...
  • gll028
  • gll028
  • 2013-04-14 11:32
  • 1842

网络编程(35)—— 利用pthread_join函数等待线程结束并获取线程函数返回值

在linux中的多线程一节中,我们介绍了利用pthread_create()函数创建子线程的方法。这种方式创建的线程存在一个问题:在主线程创建完成子线程后,若子线程函数 还没结束时,但是此时主线程函数已经结束,那么子线程也会被强制销毁,为了避免这个问题,当时我们在主线程中sleep了11秒钟以等待子...
  • lzhui1987
  • lzhui1987
  • 2016-12-13 09:30
  • 1467

线程的interrupt中断和取消机制

每一个线程都有一个boolean类型的中断状态。当中断线程时,这个线程的中断状态将被设置为true,在Thread中,有以下三个方法中断线程和查询线程中断状态的方法。 public class Tread{     public void interrupt(){....
  • wjbtian
  • wjbtian
  • 2014-04-08 15:40
  • 1623

线程的分离与非分离状态

线程的分离状态决定一个线程以什么样的方式来终止自己     非分离 【joinable】     分离 【detached】 非分离的线程能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源(如栈)是不释放的【默认状态】 分离的线程是...
  • gongyuan073
  • gongyuan073
  • 2017-02-04 15:00
  • 844

并发编程(4)线程的生命周期及基本状态

线程从创建到最终的消亡,要经历若干个状态,一般来说,线程包括以下这几个状态:新建(new),可运行(runnable),运行(running),阻塞(blocked),死亡(dead)。 先来一张经典图: .1)新建(new)      &#...
  • u012170724
  • u012170724
  • 2016-03-24 16:04
  • 460

线程的取消/撤销

线程创建 1.1 线程与进程 相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。在串行程序基础上引入线程和进程是为了提高程序的并发度,从而提高程序运行效率和响应时间。 线程和进程在使用上各有优缺点:线...
  • sinat_24143931
  • sinat_24143931
  • 2016-03-06 09:01
  • 950

线程自我终止会导致线程内部对象的析构异常?

一开始主线程A是作为一个对话框CTestDlg存在,现在,在CTestDlg的成员函数OnStartThread中开始一个新线程B,OnStartThread函数中CString局部变量strA(注1)作为线程B工作函数的参数pParam,CTestDlg的成员函数OnEndThread用于设置中止...
  • yjgx007
  • yjgx007
  • 2004-12-10 02:59
  • 2563

C#向线程传递参数和获得返回值

C#发起一个线程以后,经常需要给线程传递一些参数。总结了几种启动线程传递参数的方法。传递参数1、通过构造函数传递参数MyClass obj = new MyClass(a,b); Thread t = new Thread(new ThreadStart(obj.ThreadMethod)); t....
  • aofengdaxia
  • aofengdaxia
  • 2015-07-03 18:12
  • 7289
    个人资料
    • 访问:99531次
    • 积分:1965
    • 等级:
    • 排名:千里之外
    • 原创:92篇
    • 转载:20篇
    • 译文:0篇
    • 评论:3条
    博客公告
    最新评论