c++线程

pthread(部分内容来自菜鸟教程)

创建线程

创建一个 POSIX 线程:

c++ 复制代码
#include <pthread.h>
pthread_create (thread, attr, start_routine, arg) 

pthread_create 创建一个新的线程,并让它可执行。

参数:

  • thread :指向线程标识符指针
  • attr :一个不透明的属性对象,可以被用来设置线程属性。可以指定线程属性对象,也可以使用默认值 NULL。
  • start_routine :线程运行函数起始地址,一旦线程被创建就会执行。
  • arg :运行函数的参数。它必须通过把引用作为指针强制转换为 void 类型进行传递。如果没有传递参数,则使用 NULL。

终止线程

c++ 复制代码
#include <pthread.h>
pthread_exit (status) 

向线程传递参数

在线程回调中传递任意的数据类型,因为它指向 void。

使用 pthread_create() 函数创建了 5 个线程,并接收传入的参数。每个线程打印一个 "Hello Runoob!" 消息,并输出接收的参数,然后调用 pthread_exit() 终止线程。

c++ 复制代码
#include <iostream>
#include <cstdlib>
#include <pthread.h>
 
using namespace std;
 
#define NUM_THREADS     5
 
struct thread_data{
   int  thread_id;
   char *message;
};
 
void *PrintHello(void *threadarg)
{
   struct thread_data *my_data;
 
   my_data = (struct thread_data *) threadarg;
 
   cout << "Thread ID : " << my_data->thread_id ;
   cout << " Message : " << my_data->message << endl;
 
   pthread_exit(NULL);
}
 
int main ()
{
   pthread_t threads[NUM_THREADS];
   struct thread_data td[NUM_THREADS];
   int rc;
   int i;
 
   for( i=0; i < NUM_THREADS; i++ ){
      cout <<"main() : creating thread, " << i << endl;
      td[i].thread_id = i;
      td[i].message = (char*)"This is message";
      rc = pthread_create(&threads[i], NULL,
                          PrintHello, (void *)&td[i]);
      if (rc){
         cout << "Error:unable to create thread," << rc << endl;
         exit(-1);
      }
   }
   pthread_exit(NULL);
}

连接和分离线程

c++ 复制代码
pthread_join (threadid, status) 
pthread_detach (threadid) 

pthread_join() 子程序阻碍调用程序,直到指定的 threadid 线程终止为止。当创建一个线程时,它的某个属性会定义它是否是可连接的(joinable)或可分离的(detached)。只有创建时定义为可连接的线程才可以被连接。如果线程创建时被定义为可分离的,则它永远也不能被连接。

c++ 复制代码
#include <iostream>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>
 
using namespace std;
 
#define NUM_THREADS     5
 
void *wait(void *t)
{
   int i;
   long tid;
 
   tid = (long)t;
 
   sleep(1);
   cout << "Sleeping in thread " << endl;
   cout << "Thread with id : " << tid << "  ...exiting " << endl;
   pthread_exit(NULL);
}
 
int main ()
{
   int rc;
   int i;
   pthread_t threads[NUM_THREADS];
   pthread_attr_t attr;
   void *status;
 
   // 初始化并设置线程为可连接的(joinable)
   pthread_attr_init(&attr);
   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
 
   for( i=0; i < NUM_THREADS; i++ ){
      cout << "main() : creating thread, " << i << endl;
      rc = pthread_create(&threads[i], NULL, wait, (void *)&i );
      if (rc){
         cout << "Error:unable to create thread," << rc << endl;
         exit(-1);
      }
   }
 
   // 删除属性,并等待其他线程
   pthread_attr_destroy(&attr);
   for( i=0; i < NUM_THREADS; i++ ){
      rc = pthread_join(threads[i], &status);
      if (rc){
         cout << "Error:unable to join," << rc << endl;
         exit(-1);
      }
      cout << "Main: completed thread id :" << i ;
      cout << "  exiting with status :" << status << endl;
   }
 
   cout << "Main: program exiting." << endl;
   pthread_exit(NULL);
}

同步实现--锁

lock有很多实现的方式,linux这里使用的是mutex,由于mutex本身是结构体,所以声名的时候别忘了init一下。

c++ 复制代码
#include "pthread.h"

pthread_mutex_t mutex; //这里要保证是global var,
pthread_mutex_init(&mutex, NULL); //init mutex,

pthread_mutex_lock(&mutex); //准备进入临界区

// critical section //

pthread_mutex_unlock(&mutex); //释放锁,别的线程可能要使用
pthread_mutex_destroy(&mutex); // 销毁锁,锁不能在用了

Condition vatiables(cv)

cv的含义其实有点偏向于MPI的思想,传递消息;可以让threads wait,也可以notify or broadcast线程让他们起来干活。主要的操作有一下三种:

c++ 复制代码
phtread_cond_init();

pthread_cond_wait(&theCV,&somelock); //这里sleep线程的同时也将somelock释放掉了,要不其他线程无法取得lock就没办法执行(甚至是叫醒它了)
pthread_cond_signal(&theCV); 
pthread_cond_boardcast(&theCV);

在临界区中处理写share量的时候,需要保证不同线程对其的访问是可控的,否则可能在不同线程中读取到的写share量不一致进而影响CV的工作,因为一般情况下临界区中的写share量就是我们CV工作中的重要判断量。因此,虽然这个条件相对严格,但是是有必要的。

semaphore

这个东西可以看作是lock的一个自然延申。也就是一个资源可以同时被多少执行单元使用。我们之前讲到的lock就可以看做是一个binary semaphore。这里就只是简要的谈谈,因为这个东西使用的时候很让人头大,弄不好就会死锁。而且虽然semaphore属于POSIX标准,但是严格来讲的话,它不属于pthread。

c++ 复制代码
#include <semaphore.h>

sem_t sem;
sem_init(&sem);
sem_wait(&sem);

// critical section

sem_post(&sem);
sem_destroy(&sem);

semaphore可以控制同一个时间有多少thread可以访问临界区,需要注意的就是上面声明--释放的匹配关系不要忘记。

std::thread

创建线程

通过 detach() 函数,将子线程和主线分离,子线程可以独立继续运行,即使主线程结束,子线程也不会结束。

c++ 复制代码
#include <iostream>
#include <thread>
using namespace std::literals::chrono_literals;
using namespace std;

void test() {
    cout << "Hello World" << endl;
}

int main() {
    std::thread t1(test);
    t1.detach();
    this_thread::sleep_for(10ms); // c++ 17
    // 低于C++17使用这行代码  this_thread::sleep_for(chrono::milliseconds(10));
    return 0;
}

传递参数

join() 函数可以在当前线程等待线程运行结束。

c++ 复制代码
#include <iostream>
#include <thread>
using namespace std::literals::chrono_literals;
using namespace std;

void test(int Id,string name) {
    cout << "Hello World" << endl;
        cout << "Id : " <<Id<<" Name : "<<name<< endl;
}

int main() {
    std::thread t1(test,12,"haha");
    t1.join();
    this_thread::sleep_for(10ms); // c++ 17
    // 低于C++17使用这行代码  this_thread::sleep_for(chrono::milliseconds(10));
    return 0;
}

线程休眠

c++ 复制代码
using namespace std::literals::chrono_literals;
// 让当前线程睡眠 10 毫秒
this_thread::sleep_for(10ms);
// 低于C++17使用这行代码  this_thread::sleep_for(chrono::milliseconds(10));
// 让当前线程睡眠 5 秒
this_thread::sleep_for(5s);

condition_variable

使用 condition_variable 实现生产者和消费者的实验,通过 wait 进入线程等待,知道有其它的线程把当前线程唤醒。

当 std::condition_variable 对象的某个 wait 函数被调用的时候,它使用 std::unique_lock(通过 std::mutex) 来锁住当前线程。当前线程会一直被阻塞,直到另外一个线程在相同的 std::condition_variable 对象上调用了 notification 函数来唤醒当前线程。

当前线程调用 wait() 后将被阻塞(调用wait()前先获取线程锁std::unique_lockstd::mutex lock(g_mutex))同时会自动调用 unlock() 释放锁(g_mutex),直到另外某个线程调用 notify_*(g_con.notify_one()、g_con.notify_all()) 唤醒了当前线程。唤醒当前线程时wait() 函数也是自动调用 lock()(g_mutex:加锁,进入临界区),使得 g_mutex 的状态和 wait 函数被调用时相同。

在线程被阻塞时,该函数会自动调用 lck.unlock() 释放锁,使得其他被阻塞在锁竞争上的线程得以继续执行。另外,一旦当前线程获得通知(notified,另外某个线程调用 notify_* 唤醒了当前线程),wait() 函数也是自动调用 lck.lock(),使得 lck 的状态和 wait 函数被调用时相同。

c++ 复制代码
#include <iostream>
#include <thread>
#include <list>
using namespace std::literals::chrono_literals;
using namespace std;

std::mutex g_mutex; // 线程锁
condition_variable g_con; // cv

list<int> products;

void test() {
    int product_id = 0;
    while (true) {
        products.push_back(++product_id);
        cout << "products 生产: " << product_id << endl;
        std::unique_lock<std::mutex> lock(g_mutex);  // 进入临界区
        // 通知消费者消费
        g_con.notify_one(); 						// 唤醒 g_con.wait(lock)
        // g_con.notify_all(); // 唤醒所有线程(多个线程同时使用 g_con g_mutex)
        lock.unlock(); 								// 提前释放锁,供其他线程进入临界区
        if (product_id > 50) {
            break;
        }
        this_thread::sleep_for(2ms);
    }
}

int main() {
    std::thread t1(test);
    while (true) {
        std::unique_lock<std::mutex> lock(g_mutex); // 离开代码作用域后自动解锁
        if (products.empty()) {
            cout << "没有产品,等待" << endl;
            // 进入等待,知道有新产品
            g_con.wait(lock);  // 释放锁 线程阻塞;唤醒时,自动加锁,与调用wait函数之前一行
            
   			if(!products.empty()){
                int product_id = products.front();
                products.pop_front();
                cout << "消费产品 " << product_id << endl;
                this_thread::sleep_for(2ms);
                if (product_id > 50) break;                
            }
        }
    }
    t1.join();
    return 0;
}

std::unique_lock<std::mutex> 出作用域后会自动解锁

thread_local

C++11中提供了thread_local,thread_local定义的变量在每个线程都保存一份副本,而且互不干扰,在线程退出的时候自动销毁。

c++ 复制代码
#include <iostream>
#include <thread>
using namespace std::literals::chrono_literals;
using namespace std;
thread_local int t_l_counter = 0;

void test() {
    cout << "flag1 t_l_counter: " << t_l_counter << endl;
    t_l_counter = 2;
}

int main() {
    t_l_counter = 1;
    std::thread t1(test);
    t1.join();
    cout << "flag2 t_l_counter: " << t_l_counter << endl;
    return 0;
}

同步锁

如果需要同一时间只有一个线程在test函数中执行代码,那么就要加锁,lock() 用于加锁,而unlock() 解锁。

c++ 复制代码
#include <iostream>
#include <thread>
using namespace std::literals::chrono_literals;
using namespace std;
std::mutex g_mutex;

void test() {
    g_mutex.lock();
    cout << "task start thread ID: " << this_thread::get_id() << endl;
    this_thread::sleep_for(10ms);
    cout << "task end thread ID: " << this_thread::get_id() << endl;
    g_mutex.unlock();
}
int main() {
    std::thread t1(test);
    std::thread t2(test);
    std::thread t3(test);
    t1.join();
    t2.join();
    t3.join();
    return 0;
}

除了std::mutex(非递归的互斥量),还有std::timed_mutex(带超时的非递归互斥量),std::recursive_mutex(递归互斥量)、std::recursive_timed_mutex(带超时的递归互斥量)。

lambda在线程中的使用

lambda 的语法

c++ 复制代码
[capture](parameters) mutalble->return-type{statement};

编译器会自动生成一个匿名类,该类重载了 () 运算符。

c++ 复制代码
int id = 0;
auto f = [id]() mutable {
    cout << "id: " << id << endl;
    ++id;
}

capture

  • [] :什么也不捕获
  • [=] : 按值的方式捕获所有变量
  • [&] : 按引用方式捕获所有变量
  • [boo] : 值捕获boo的值
  • [=,&a] : 按值捕获所有局部变量,按引用捕获变量a
  • [=,&a,&b,&c] : 同上
  • [&,a] : 按引用捕获所有局部变量,按值捕获方式捕获a
  • [&,a,b,c] : 同上
  • [this] : 在成员函数中,直接捕获this指针

mutable

值捕获后,在匿名函数中对该值是不能做修改的,如果想要做修改,必须加上 mutable 关键字,并且在匿名函数中做的修改结果在函数外是不会生效的。

parameters

参数列表也是可以将外部的值传递给匿名函数内部的;与正常函数的形参一样。

return-type

对于编译器能自动推导的返回类型,可以省略 return-type,但是如果无法推导的类型,就必须添加上返回类型

当函数不止一个return语句时,就需要加上返回类型了

线程中使用lambda

c++ 复制代码
#include <iostream>
#include <thread>
using namespace std;
int main() {
    std::thread t1([]{
        cout << "task start thread ID: " << this_thread::get_id() << endl;
    }};
    t1.join();
    return 0;
}
相关推荐
奋斗的小花生4 分钟前
c++ 多态性
开发语言·c++
魔道不误砍柴功6 分钟前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
闲晨9 分钟前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程36 分钟前
一个例子来说明Ada语言的实时性支持
开发语言·ada
UestcXiye2 小时前
《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
c++·计算机网络·ip·tcp
Chrikk2 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*2 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue2 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man2 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
霁月风3 小时前
设计模式——适配器模式
c++·适配器模式