Linux 线程

进程与线程

进程:拥有PCB,有独立地址空间。最小的分配资源单位,多个线程共享进程的资源。
线程:拥有PCB,没有独立地址空间。轻量级的进程,本质是进程。是CPU最小的执行和调度单位。

线程的优缺点

  1. 可以在一个进程内实现并发。
  2. 开销少,创建线程比创建进程要快。
  3. 数据通信、数据共享方便,同时也增加了开发的难度。

线程的创建和终止

线程创建、等待线程退出、查看线程

pthread_create 的 api 如下(第一个参数是线程id号,第二个参数用来设置线程属性,第三个是线程函数,第四个参数是运行函数的参数)

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

pthread_join的 api 如下(第一个是线程id,第二个默认空)作用是等待线程结束。

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

查看线程的命令如下:(本例中一个主线程,两个子线程)

[wsm@iZ2vchi15pdv9v0bwzok5kZ pthread]$ ps -ef | grep book1
wsm      22312 18702  0 14:06 pts/5    00:00:00 ./book1
wsm      22347 19007  0 14:07 pts/6    00:00:00 grep --color=auto book1

[wsm@iZ2vchi15pdv9v0bwzok5kZ pthread]$ ps -Lf 22312
UID        PID  PPID   LWP  C NLWP STIME TTY      STAT   TIME CMD
wsm      22312 18702 22312  0    3 14:06 pts/5    Sl+    0:00 ./book1
wsm      22312 18702 22313  0    3 14:06 pts/5    Sl+    0:00 ./book1
wsm      22312 18702 22314  0    3 14:06 pts/5    Sl+    0:00 ./book1

线程非正常终止

  1. 如果主线程退出,全部线程将强行终止
  2. 在子线程调用exit()将终止整个进程(调用pthread_exit(0);或return 0;不会)
  3. 缺省行为是终止程序的信号将导致整个进程终止

终止线程的三种方法

  1. 线程可以简单地从线程函数中返回,返回值是线程的退出码
  2. 线程可以被同一进程中的其它线程调用pthread_cancel()取消
  3. 在线程函数中,调用pthread_exit()退出
return 0;

if (ii == 3) pthread_cancel(thid2);

if (ii == 3) pthread_exit(0);

pthread_exit 可以写到不在主线程函数里面的地方

#include <iostream>
#include <pthread.h>

void f()
{
    pthread_exit(0);
}

void* hello(void* arg) {
    std::cout << "Hello from thread!" << std::endl;
    for(int i = 0; i < 5; i ++)
    {
        std::cout << i << std::endl;
        if (i == 2) f();
    }
    return nullptr;
}

int main() {
    pthread_t tid; // 线程标识符

    // 创建一个新线程,将 hello 函数作为线程函数
    if (pthread_create(&tid, nullptr, hello, nullptr) != 0) {
        std::cerr << "Error creating thread" << std::endl;
        return 1;
    }

    // 等待新线程完成
    if (pthread_join(tid, nullptr) != 0) {
        std::cerr << "Error joining thread" << std::endl;
        return 2;
    }

    // 主线程继续执行
    std::cout << "Hello from main!" << std::endl;

    return 0;
}

线程参数的传递

  1. 创建的多个线程并不保证哪个线程先运行
  2. 不能用全局变量代替线程函数的参数
  3. 数据类型的强制转换

如何传递整形参数

int var = 1;
if (pthread_create(&thid1,NULL,thmain1, (void *)(long)var)!=0) { printf("pthread_create failed.\n"); exit(-1); }

int 转成 void*,4字节转8字节,要先转成long。在线程里面使用的时候要先转成long再转成int。

如何传递地址参数

传的时候注意给每个线程传一个专属的地址。

把多个参数放到一个结构体中就可以通过传地址一次传多个参数。

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

void *thmain(void *arg);    // 线程的主函数。

struct st_args
{
  int  no;        // 线程编号。
  char name[51];  // 线程名。
};

int main(int argc,char *argv[])
{
  pthread_t thid=0;

  // 创建线程。
  struct st_args *stargs=new struct st_args;
  stargs->no=15;   strcpy(stargs->name,"测试线程");
  if (pthread_create(&thid,NULL,thmain,stargs)!=0) { printf("pthread_create failed.\n"); exit(-1); }

  // 等待子线程退出。
  printf("join...\n");
  pthread_join(thid,NULL);
  printf("join ok.\n");
}

void *thmain(void *arg)    // 线程主函数。
{
  struct st_args *pst=(struct st_args *)arg;
  printf("no=%d,name=%s\n",pst->no,pst->name);
  delete pst;
  printf("线程开始运行。\n");
}

线程退出状态

线程退出的状态

struct st_ret *pst=0;
pthread_join(thid,(void **)&pst);
printf("retcode=%d,message=%s\n",pst->retcode,pst->message);

线程资源的回收,让多线程程序有计划的退出

分离线程的apipthread_detach(pthread_self());,线程结束后,由系统回收资源。

detach后不需要join

如何实现有计划的退出?先create主线程,主线程再将计划的清理函数入栈出栈

// 本程序演示线程资源的回收(线程清理函数)。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

void *thmain(void *arg);    // 线程主函数。

void  thcleanup1(void *arg);    // 线程清理函数1。
void  thcleanup2(void *arg);    // 线程清理函数2。
void  thcleanup3(void *arg);    // 线程清理函数3。

int main(int argc,char *argv[])
{
  pthread_t thid;

  // 创建线程。
  if (pthread_create(&thid,NULL,thmain,NULL)!=0) { printf("pthread_create failed.\n"); exit(-1); }

  int result=0;
  void *ret;
  printf("join...\n");
  result=pthread_join(thid,&ret);   printf("thid result=%d,ret=%ld\n",result,ret);
  printf("join ok.\n");
}

void *thmain(void *arg)    // 线程主函数。
{
  pthread_cleanup_push(thcleanup1,NULL);  // 把线程清理函数1入栈(关闭文件指针)。
  pthread_cleanup_push(thcleanup2,NULL);  // 把线程清理函数2入栈(关闭socket)。
  pthread_cleanup_push(thcleanup3,NULL);  // 把线程清理函数3入栈(回滚数据库事务)。 

  for (int ii=0;ii<3;ii++)
  {
    sleep(1); printf("pthmain sleep(%d) ok.\n",ii+1);
  }

  pthread_cleanup_pop(1);  // 把线程清理函数3出栈。
  pthread_cleanup_pop(1);  // 把线程清理函数2出栈。
  pthread_cleanup_pop(1);  // 把线程清理函数1出栈。
}

void  thcleanup1(void *arg)
{
  // 在这里释放关闭文件、断开网络连接、回滚数据库事务、释放锁等等。
  printf("cleanup1 ok.\n");
};

void  thcleanup2(void *arg)
{
  // 在这里释放关闭文件、断开网络连接、回滚数据库事务、释放锁等等。
  printf("cleanup2 ok.\n");
};

void  thcleanup3(void *arg)
{
  // 在这里释放关闭文件、断开网络连接、回滚数据库事务、释放锁等等。
  printf("cleanup3 ok.\n");
};

线程的取消

pthread_cancel

有两个模式,异步撤销(立即撤销)和延迟撤销。延迟撤销到达撤销点时才会撤销。

比如这个程序,不加撤销点的话将把循环跑满。

int main(int argc,char *argv[])
{
  pthread_t thid;

  // 创建线程。
  if (pthread_create(&thid,NULL,thmain,NULL)!=0) { printf("pthread_create failed.\n"); exit(-1); }

  usleep(100); pthread_cancel(thid);

  int result=0;
  void *ret;
  printf("join...\n");
  result=pthread_join(thid,&ret);   printf("thid result=%d,ret=%ld\n",result,ret);
  printf("join ok.\n");

  printf("var=%d\n",var);
}

void *thmain(void *arg)    // 线程主函数。
{
  // pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);

  for (var=0;var<400000000;var++)
  {
    ;
    pthread_testcancel();  // 撤销点
  }
  return (void *) 1;
}

线程和信号

  1. 在多线程程序中,外部向进程发送信号不会中断系统调用
  2. 在多线程中,信号的处理是所有线程共享的(子线程会覆盖主线程的信号处理)
  3. 进程中的信号可以送达单个线程,会中断系统调用

线程安全

  1. 多个线程访问共享资源(全局和静态)的时候会冲突
  2. 三个概念:原子性、可见性、顺序性

如何解决线程安全?

  1. volatile关键字
  2. 原子操作(原子类型)
  3. 线程同步(锁)

file

原子操作本质是总线锁,CPU和内存通过总线进行数据交换,执行完汇编后再放开锁。硬件级别的锁,比线程库提供的锁快十倍左右。

线程同步

  1. 互斥锁
  2. 自旋锁
  3. 读写锁
  4. 条件变量
  5. 信号量

互斥锁

初始化互斥锁有两种方式

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;      // 声明互斥锁。

pthread_mutex_init(&mutex,NULL);   // 初始化互斥锁。

加锁解锁

pthread_mutex_lock(&mutex);    // 加锁。
var++;
pthread_mutex_unlock(&mutex);  // 解锁。

销毁互斥锁

pthread_mutex_destroy(&mutex);  // 销毁锁。

自旋锁

初始化

pthread_spinlock_t spin;      // 声明自旋锁。
pthread_spin_init(&spin,PTHREAD_PROCESS_PRIVATE);   // 初始化自旋锁。

加锁解锁

pthread_spin_lock(&spin);    // 加锁。
var++;
pthread_spin_unlock(&spin);  // 解锁。

销毁

pthread_spin_destroy(&spin);  // 销毁锁。

读写锁

  1. 只要没有线程持有写锁,任意线程都可以成功申请读锁。
  2. 只有再不加锁状态时,才能成功申请写锁。

注意事项

  1. 读写锁适合于对读的次数远大于写的情况
  2. Linux系统优先考虑读锁,这种实现方式有可能导致写入线程饿死的情况
// 本程序演示线程同步-读写锁。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>

pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER;   // 声明读写锁并初始化。

void *thmain(void *arg);    // 线程主函数。

void handle(int sig);       // 信号15的处理函数。

int main(int argc,char *argv[])
{
  signal(15,handle);       // 设置信号15的处理函数。

  pthread_t thid1,thid2,thid3;

  // 创建线程。
  if (pthread_create(&thid1,NULL,thmain,NULL)!=0) { printf("pthread_create failed.\n"); exit(-1); }
sleep(1);
  if (pthread_create(&thid2,NULL,thmain,NULL)!=0) { printf("pthread_create failed.\n"); exit(-1); }
sleep(1);
  if (pthread_create(&thid3,NULL,thmain,NULL)!=0) { printf("pthread_create failed.\n"); exit(-1); }

  // 等待子线程退出。
  pthread_join(thid1,NULL);  pthread_join(thid2,NULL);  pthread_join(thid3,NULL);  

  pthread_rwlock_destroy(&rwlock);  // 销毁锁。
}

void *thmain(void *arg)    // 线程主函数。
{
  for (int ii=0;ii<100;ii++)
  {
    printf("线程%lu开始申请读锁...\n",pthread_self());
    pthread_rwlock_rdlock(&rwlock);    // 加锁。
    printf("线程%lu开始申请读锁成功。\n\n",pthread_self());
    sleep(5);
    pthread_rwlock_unlock(&rwlock);    // 解锁。
    printf("线程%lu已释放读锁。\n\n",pthread_self());

    if (ii==3) sleep(8);
  }
}

void handle(int sig)       // 信号15的处理函数。
{
  printf("开始申请写锁...\n");
  pthread_rwlock_wrlock(&rwlock);    // 加锁。
  printf("申请写锁成功。\n\n");
  sleep(10);
  pthread_rwlock_unlock(&rwlock);    // 解锁。
  printf("写锁已释放。\n\n");
}

读锁线程有sleep 8秒的操作,目的是测试,写锁可以被申请到 :joy:

条件变量

  1. 与互斥锁一起使用
  2. 实现生产消费者模型
  3. 实现通知功能

条件变量初使用

// 本程序演示线程同步-条件变量。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>

pthread_cond_t cond=PTHREAD_COND_INITIALIZER;     // 声明条件变量并初始化。
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;  // 声明互斥锁并初始化。

void *thmain(void *arg);    // 线程主函数。

void handle(int sig);       // 信号15的处理函数。

int main(int argc,char *argv[])
{
  signal(15,handle);       // 设置信号15的处理函数。

  pthread_t thid1,thid2,thid3;

  // 创建线程。
  if (pthread_create(&thid1,NULL,thmain,NULL)!=0) { printf("pthread_create failed.\n"); exit(-1); }
  if (pthread_create(&thid2,NULL,thmain,NULL)!=0) { printf("pthread_create failed.\n"); exit(-1); }
  if (pthread_create(&thid3,NULL,thmain,NULL)!=0) { printf("pthread_create failed.\n"); exit(-1); }

  // 等待子线程退出。
  pthread_join(thid1,NULL);  pthread_join(thid2,NULL);  pthread_join(thid3,NULL);  

  pthread_cond_destroy(&cond);    // 销毁条件变量。
  pthread_mutex_destroy(&mutex);  // 销毁互斥锁。
}

void *thmain(void *arg)    // 线程主函数。
{
  while (true)
  {
    printf("线程%lu开始等待条件信号...\n",pthread_self());
    pthread_cond_wait(&cond,&mutex);    // 等待条件信号。
    printf("线程%lu等待条件信号成功。\n\n",pthread_self());
  }
}

void handle(int sig)       // 信号15的处理函数。
{
  printf("发送条件信号...\n");
  pthread_cond_signal(&cond);      // 唤醒等待条件变量的一个线程。
  // pthread_cond_broadcast(&cond);   // 唤醒等待条件变量的全部线程。
}

信号量

演示信号量的简单使用

// 本程序演示线程同步-信号量。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>

volatile int var;

sem_t sem;      // 声明信号量。

void *thmain(void *arg);    // 线程主函数。

int main(int argc,char *argv[])
{
  sem_init(&sem,0,1);   // 初始化信号量。

  pthread_t thid1,thid2;

  // 创建线程。
  if (pthread_create(&thid1,NULL,thmain,NULL)!=0) { printf("pthread_create failed.\n"); exit(-1); }
  if (pthread_create(&thid2,NULL,thmain,NULL)!=0) { printf("pthread_create failed.\n"); exit(-1); }

  // 等待子线程退出。
  printf("join...\n");
  pthread_join(thid1,NULL);  
  pthread_join(thid2,NULL);  
  printf("join ok.\n");

  printf("var=%d\n",var);

  sem_destroy(&sem);  // 销毁信号量。
}

void *thmain(void *arg)    // 线程主函数。
{
  for (int ii=0;ii<1000000;ii++)
  {
    sem_wait(&sem);    // 加锁。
    var++;
    sem_post(&sem);  // 解锁。
  }
}

生产消费者模型

条件变量的P操作解开互斥锁

开发多线程的网络服务端程序

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇