How to use Thread

本文详细介绍了Java中使用Thread类和实现Runnable接口创建线程的方法,并通过实例展示了如何实现线程间的同步控制,包括使用sleep()、synchronized关键字及wait()和notify()方法来解决线程同步问题。

1. how to use thread

Two things need to know: Thread class and Runnable interface.

We can instanciate a thread class to be a thread, we can also create a normal class and let this class implements Runnable interface to make it a thread. Because thread class itself also has implemented the Runnable interface. Both of them have to overwrite the run() method which is inherited from Runnable interface.

2. the first way: using Thread class

In this way, we need to declare a class to be a subclass of Thread. This subclass should override therun method of classThread. An instance of the subclass can then be allocated and started. For example, a thread that computes primes larger than a stated value could be written as follows:

     class PrimeThread extends Thread {
         long minPrime;
         PrimeThread(long minPrime) {
             this.minPrime = minPrime;
         }
        /**this constructor is defined by the user and is more convenient,like this way
         * we need to count the minimum prime number, we can use this constructor to pass
         * the unmber we need and to handle it in the run() method.
        **/
     public void run() {
             // compute primes larger than minPrime
              . . .
         }
     }

the following code is the way to start the thread:

  PrimeThread p = new PrimeThread(143);
     p.start();

3. the seccond way: normal class implement the runnable interface

The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creatingThread, and started. The same example in this other style looks like the following:

     class PrimeRun implements Runnable {
         long minPrime;
         PrimeRun(long minPrime) {
             this.minPrime = minPrime;
         }
 
         public void run() {
             // compute primes larger than minPrime
              . . .
         }
     }

the following code is the way to create the thread and start it

PrimeRun p = new PrimeRun(143);
     new Thread(p).start();

here we usethe class PrimeRun as an parameter of the Thread class. we can have a look at the constructor of the Thread class in the API

we have a class p which has implemented Runnable interface, here we pass it as an parameter of Runnable, so Thread(p).start() acctually calls p's run() method and can only call this one method. As we can see there is a Polimorphism here, and we know the run() method called is the one overwrited in class p not the one in its super class Runnable.


4. sleep()

import java.util.*;
public class TestInterrupt {
  public static void main(String[] args) {
    MyThread thread = new MyThread();
    thread.start();
    try {Thread.sleep(10000);}
    //this means the main thread will sleep 10 seconds
    catch (InterruptedException e) {}
    thread.interrupt();
    //interrupt the main thread,which will also make mythread interruptted
   }
}

class MyThread extends Thread {
	boolean flag = true;
  public void run(){
    while(flag){
      System.out.println("==="+new Date()+"===");
      try {
        sleep(1000);
       //we can use Thread.sleep(1000) if MyThread implements Runnable
      } catch (InterruptedException e) {
        return;
      }
    }
  }
}


5.Synchronized key word

If there is a synchronized method m1(), and there are 2 thread running, both of which can call the m1 mehtod, synchronized promise that at the same time there is only one thread can call this method.

see this two examples blew: 

Example 1

package com.thread;

public class TT implements Runnable {
	int b = 100;
	
	public synchronized void m1() throws Exception{
		b = 1000;
		Thread.sleep(5000);
		System.out.println("b = " + b);
	}
	
	public void m2() throws Exception {
		Thread.sleep(2500);
		b = 2000;
	}
	
	public void run() {
		try {
			m1();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) throws Exception {
		TT tt = new TT();
		Thread t = new Thread(tt);
		t.start();
		/** t.start() doesn't means that the thread t is now executing, it only let t
		 * goes to the "ready" stage, so we need add Thread.sleep(1000) below to make
		 * the main thread sleep and the thread t will go from "ready" to "start"
		 */
		Thread.sleep(1000);
		tt.m2();
		System.out.println(tt.b);
	}
}
result:   2000           b=2000


In the main method, t.start() doesn't means that the thread t is now executing, it only let t goes to the "ready" stage, so we need add Thread.sleep(1000) below to makethe main thread sleep and the thread t will go from "ready" to "start". So it goes into synchronized m1(), in m1, it changed b to 1000 and it went into sleep. During its sleep, main thread wake up and calls m2(), in m2 method it needs to change b's value to 2000. Even though at this tiime m1 is not finished, and b is now locked by m1(), m2 method cal still change the value of b. so it changed b's value to 2000. The main thread printed out b's vaue: 2000 and m1 wake up and print: b=2000.

At this point, we can see the key word doesn't fullfill its responsibility. what we want is even though m1 is sleeping now we want m2 wait until m1 has printed out b's value.

example 2

package com.thread;

public class TT implements Runnable {
	int b = 100;
	
	public synchronized void m1() throws Exception{
		b = 1000;
		Thread.sleep(5000);
		System.out.println("b = " + b);
	}
	
	public synchronized void m2() throws Exception {
		Thread.sleep(2500);
		b = 2000;
	}
	
	public void run() {
		try {
			m1();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) throws Exception {
		TT tt = new TT();
		Thread t = new Thread(tt);
		t.start();
		/** t.start() doesn't means that the thread t is now executing, it only let t
		 * goes to the "ready" stage, so we need add Thread.sleep(1000) below to make
		 * the main thread sleep and the thread t will go from "ready" to "start"
		 */
		Thread.sleep(1000);
		tt.m2();
		System.out.println(tt.b);
	}
}
output:    b=1000           2000


In example 2, we only added the key word "synchronized " for m2. In this way when m1 is sleeping tt.m2() won't execute because both of them are synchronized m2 has too wait until m1 wake up and finished its work. We may think: is it because both m1 and m2 are locking b, since m1 was called before m2, so m2 has to wait ? what about the object m2 is locking c not b?

example 3:


package com.thread;

public class TT implements Runnable {
	int b = 100;
	int c = 20;
	
	public synchronized void m1() throws Exception{
		b = 1000;
		Thread.sleep(5000);
		System.out.println("b = " + b);
	}
	
	public synchronized void m2() throws Exception {
		//Thread.sleep(2500);
		c = 2000;
		System.out.println("c = " + c);
	}
	
	public void run() {
		try {
			m1();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) throws Exception {
		TT tt = new TT();
		Thread t = new Thread(tt);
		t.start();
		/** t.start() doesn't means that the thread t is now executing, it only let t
		 * goes to the "ready" stage, so we need add Thread.sleep(1000) below to make
		 * the main thread sleep and the thread t will go from "ready" to "start"
		 */
		Thread.sleep(1000);
		tt.m2();
		System.out.println(tt.b);
	}
}

In this way, m2 locks c , m1 locks b, they are not disturbing each other, but still, m2 need to wait  until m1 wake up and finshes its work.


6.producer and consumer(   wait() and notify()  )

package com.thread;
public class ProducerConsumer {
	public static void main(String[] args) {
		SyncStack ss = new SyncStack();
		Producer p = new Producer(ss);
		Consumer c = new Consumer(ss);
		new Thread(p).start();
		new Thread(p).start();
		new Thread(p).start();
		new Thread(c).start();
	}
}

class WoTou {
	int id; 
	WoTou(int id) {
		this.id = id;
	}
	public String toString() {
		return "WoTou : " + id;
	}
}

class SyncStack {
	int index = 0;
	WoTou[] arrWT = new WoTou[6];
	
	public synchronized void push(WoTou wt) {
		while(index == arrWT.length) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.notifyAll();		
		arrWT[index] = wt;
		index ++;
	}
	
	public synchronized WoTou pop() {
		while(index == 0) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.notifyAll();
		index--;
		return arrWT[index];
	}
}

class Producer implements Runnable {
	SyncStack ss = null;
	Producer(SyncStack ss) {
		this.ss = ss;
	}
	
	public void run() {
		for(int i=0; i<20; i++) {
			WoTou wt = new WoTou(i);
			ss.push(wt);
System.out.println("生产了:" + wt);
			try {
				Thread.sleep((int)(Math.random() * 200));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}			
		}
	}
}

class Consumer implements Runnable {
	SyncStack ss = null;
	Consumer(SyncStack ss) {
		this.ss = ss;
	}
	
	public void run() {
		for(int i=0; i<20; i++) {
			WoTou wt = ss.pop();
System.out.println("消费了: " + wt);
			try {
				Thread.sleep((int)(Math.random() * 1000));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}			
		}
	}
}


<think>我们正在讨论如何在ROS 2开发环境中使用GDB调试通过colcon build构建的程序。用户的问题是如何在colcon build命令中使用GDB进行调试。 关键点: 1. colcon build是ROS 2的构建工具,用于构建工作空间中的包。 2. 使用GDB调试ROS 2节点,需要确保在构建时生成调试信息(即使用-g选项)。 3. 通常,colcon build默认情况下可能不会包含调试符号,因此我们需要在构建时启用调试信息。 步骤: 第一步:在构建时启用调试信息 我们可以通过以下方式在colcon build时启用调试符号: - 使用`--cmake-args`传递`-DCMAKE_BUILD_TYPE=RelWithDebInfo`(推荐)或`Debug`。 例如: colcon build --cmake-args -DCMAKE_BUILD_TYPE=RelWithDebInfo 注意:RelWithDebInfo是带有调试信息的发布版本,Debug则是完全调试版本(无优化,带有调试信息)。 第二步:运行节点并附加GDB 有两种常见方法: 方法1:直接使用GDB运行节点可执行文件 1. 找到构建后的可执行文件(通常在install/<package_name>/lib/<package_name>目录下)。 2. 使用GDB运行该可执行文件:gdb --args path/to/executable [args] 方法2:通过ros2 launch或ros2 run启动节点并附加GDB 由于ROS 2节点通常通过ros2 run或ros2 launch启动,我们可以通过修改启动文件或使用launch-prefix来附加GDB。 例如,在ros2 run中: ros2 run --prefix 'gdb -ex run --args' <package_name> <executable_name> 或者,在launch文件中,可以为节点添加launch-prefix属性: <node pkg="package_name" exec="executable_name" name="node_name" launch-prefix="xterm -e gdb -ex run --args" /> 注意:上述launch-prefix示例中使用了xterm来打开一个新终端运行GDB。也可以直接使用`gdb -ex run --args`,但这样会占用当前终端。 第三步:调试 在GDB中,可以使用常见的GDB命令进行调试,例如: - break:设置断点 - run:运行程序(如果使用--prefix或launch-prefix时已经加了`-ex run`,则程序会自动运行) - next, step, continue等 另外,也可以使用DDD或Qt Creator等图形前端。 注意:如果程序在启动时需要传递参数,需要在GDB命令中指定。 示例:调试一个名为“my_node”的节点 1. 构建时启用调试信息: colcon build --packages-select my_package --cmake-args -DCMAKE_BUILD_TYPE=RelWithDebInfo 2. 使用ros2 run并附加GDB: ros2 run --prefix 'gdb -ex run --args' my_package my_node 3. 或者在launch文件中: import launch import launch_ros.actions def generate_launch_description(): return launch.LaunchDescription([ launch_ros.actions.Node( package='my_package', executable='my_node', name='my_node', launch_prefix=['xterm', '-e', 'gdb', '-ex', 'run', '--args'] ) ]) 注意:如果使用xterm,确保系统已安装xterm。 第四步:在GDB中设置断点等 在程序运行之前,可以在GDB命令行中设置断点。如果使用了`-ex run`,程序会立即运行,所以如果需要先设置断点,可以去掉`-ex run`,然后手动输入run。 替代方法:在程序运行后附加GDB 也可以先运行节点,然后获取其进程ID,再用GDB附加: 1. 运行节点(通过ros2 run或ros2 launch) 2. 在另一个终端中,使用`ps -aux | grep my_node`找到进程ID 3. 使用`gdb -p <pid>`附加到该进程 但是,这种方法可能无法在程序启动时设置断点。 总结:在colcon build时启用调试信息,然后通过修改运行命令或launch文件来使用GDB启动节点。 引用说明: [^1] 提到在编译时启用调试符号(-g)是使用GDB的前提,这与我们的第一步一致。 [^2] 提到了使用GDB和DDD调试QEMU,虽然不直接相关,但说明图形前端DDD也是一种选择。 [^3] 展示了GDB的汇编代码查看,这可能是调试过程中用到的,但不是必须步骤。 因此,我们按照上述步骤回答用户。</think>### 如何在 `colcon build` 中使用 GDB 调试 ROS 2 程序 #### 核心步骤:构建时启用调试符号 1. **在 `colcon build` 中启用调试信息** 使用 `-DCMAKE_BUILD_TYPE=Debug` 参数确保生成调试符号: ```bash colcon build --cmake-args -DCMAKE_BUILD_TYPE=Debug ``` 或针对特定包: ```bash colcon build --packages-select YOUR_PACKAGE --cmake-args -DCMAKE_BUILD_TYPE=Debug ``` > 关键:`-DCMAKE_BUILD_TYPE=Debug` 会向 gcc 传递 `-g` 选项[^1][^3] 2. **验证调试符号** 检查生成的 ELF 文件是否包含调试信息: ```bash objdump -h install/YOUR_PACKAGE/lib/YOUR_PACKAGE/YOUR_NODE | grep debug ``` 应有输出:`.debug_info` 和 `.debug_line` --- #### 调试方法 ##### 方法一:直接附加 GDB 到节点 ```bash # 启动节点 ros2 run YOUR_PACKAGE YOUR_NODE # 获取节点 PID ps aux | grep YOUR_NODE # 附加 GDB gdb -p <PID> ``` ##### 方法二:通过 launch-prefix 启动 在 launch 文件中添加 `launch-prefix`: ```python Node( package='YOUR_PACKAGE', executable='YOUR_NODE', name='your_node', launch_prefix=['xterm', '-e', 'gdb', '-ex', 'run', '--args'] ) ``` > 这会打开新终端并在 GDB 中自动运行节点 ##### 方法三:手动启动调试 ```bash # 进入工作空间 cd ~/ros2_ws # 直接通过 GDB 启动节点 gdb --args install/YOUR_PACKAGE/lib/YOUR_PACKAGE/YOUR_NODE ``` --- #### 调试操作示例 在 GDB 会话中: ```gdb (gdb) break main # 在 main 函数设断点 (gdb) run # 启动程序 (gdb) next # 单步执行 (gdb) print variable_name # 查看变量值 (gdb) backtrace # 查看调用栈 ``` --- #### 常见问题解决 1. **"No symbol table" 错误** 确保构建时包含 `-DCMAKE_BUILD_TYPE=Debug`,并清理重建: ```bash rm -rf build install log colcon build --cmake-args -DCMAKE_BUILD_TYPE=Debug ``` 2. **ROS 初始化失败** 在 GDB 中手动初始化 ROS: ```gdb (gdb) set args __ns:=/your_namespace ``` 3. **实时调试** 使用非优化构建: ```bash colcon build --cmake-args -DCMAKE_BUILD_TYPE=RelWithDebInfo ``` --- #### 高级技巧 - **观察点**:`watch variable_name` 监控变量变化 - **内存分析**:`x/20xw &array` 查看内存数据 - **多线程调试**:`info threads` + `thread <id>` 切换线程 - **嵌入式调试**:结合 OpenOCD 调试硬件[^2] > 提示:使用 `ddd` 替代 `gdb` 可获得图形化界面:`sudo apt install ddd`[^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值